blob: e7d1c0de67187623948f12a411bfe20211fc05ba [file] [log] [blame]
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.contacts.quickcontact;
18
Paul Soulosab54ea12014-08-28 17:22:38 -070019import android.accounts.Account;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -070020import android.animation.ArgbEvaluator;
Brian Attwellb7e43642014-06-02 14:33:04 -070021import android.animation.ObjectAnimator;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070022import android.app.Activity;
Brian Attwelld28851f2014-06-10 13:25:07 -070023import android.app.Fragment;
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +020024import android.app.LoaderManager.LoaderCallbacks;
Paul Souloseb64a4b2014-07-07 17:03:27 -070025import android.app.SearchManager;
zhuqk0617ab8d6ff2015-05-06 15:08:32 +080026import android.app.AlertDialog;
Brian Attwell752cccf2014-06-10 16:24:04 -070027import android.content.ActivityNotFoundException;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070028import android.content.ContentUris;
Paul Soulosea5e0b72014-07-08 18:09:44 -070029import android.content.ContentValues;
Brian Attwell6bb01342014-08-20 23:16:00 -070030import android.content.Context;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070031import android.content.Intent;
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +020032import android.content.Loader;
Paul Soulosdd7419d2014-07-15 11:22:13 -070033import android.content.pm.PackageManager;
34import android.content.pm.ResolveInfo;
Paul Soulos23e28362014-08-29 14:57:08 -070035import android.content.res.Resources;
zhuqk0617ab8d6ff2015-05-06 15:08:32 +080036import android.content.DialogInterface;
Brian Attwell31b2d422014-06-05 00:14:58 -070037import android.graphics.Bitmap;
Brian Attwell95c268e2014-08-26 22:04:15 -070038import android.graphics.BitmapFactory;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -070039import android.graphics.Color;
Paul Souloseb64a4b2014-07-07 17:03:27 -070040import android.graphics.PorterDuff;
41import android.graphics.PorterDuffColorFilter;
Brian Attwell31b2d422014-06-05 00:14:58 -070042import android.graphics.drawable.BitmapDrawable;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -070043import android.graphics.drawable.ColorDrawable;
Brian Attwell31b2d422014-06-05 00:14:58 -070044import android.graphics.drawable.Drawable;
Paul Souloseb64a4b2014-07-07 17:03:27 -070045import android.net.ParseException;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070046import android.net.Uri;
Paul Souloseb64a4b2014-07-07 17:03:27 -070047import android.net.WebAddress;
Brian Attwell31b2d422014-06-05 00:14:58 -070048import android.os.AsyncTask;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070049import android.os.Bundle;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -070050import android.os.Trace;
Paul Souloseb64a4b2014-07-07 17:03:27 -070051import android.provider.CalendarContract;
Brian Attwell752cccf2014-06-10 16:24:04 -070052import android.provider.ContactsContract;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070053import android.provider.ContactsContract.CommonDataKinds.Email;
Paul Souloseb64a4b2014-07-07 17:03:27 -070054import android.provider.ContactsContract.CommonDataKinds.Event;
55import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
56import android.provider.ContactsContract.CommonDataKinds.Identity;
57import android.provider.ContactsContract.CommonDataKinds.Im;
58import android.provider.ContactsContract.CommonDataKinds.Nickname;
59import android.provider.ContactsContract.CommonDataKinds.Note;
60import android.provider.ContactsContract.CommonDataKinds.Organization;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070061import android.provider.ContactsContract.CommonDataKinds.Phone;
Paul Souloseb64a4b2014-07-07 17:03:27 -070062import android.provider.ContactsContract.CommonDataKinds.Relation;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070063import android.provider.ContactsContract.CommonDataKinds.SipAddress;
64import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
65import android.provider.ContactsContract.CommonDataKinds.Website;
Brian Attwell752cccf2014-06-10 16:24:04 -070066import android.provider.ContactsContract.Contacts;
Paul Soulos60e51082014-07-10 12:33:04 -070067import android.provider.ContactsContract.Data;
Paul Soulosab54ea12014-08-28 17:22:38 -070068import android.provider.ContactsContract.Directory;
Paul Souloseb64a4b2014-07-07 17:03:27 -070069import android.provider.ContactsContract.DisplayNameSources;
Paul Soulosea5e0b72014-07-08 18:09:44 -070070import android.provider.ContactsContract.DataUsageFeedback;
Paul Soulosab54ea12014-08-28 17:22:38 -070071import android.provider.ContactsContract.Intents;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070072import android.provider.ContactsContract.QuickContact;
73import android.provider.ContactsContract.RawContacts;
Brian Attwell31b2d422014-06-05 00:14:58 -070074import android.support.v7.graphics.Palette;
Tyler Gunn03192222014-09-10 15:20:09 -070075import android.telecom.PhoneAccount;
76import android.telecom.TelecomManager;
Brian Attwellc62cc792014-10-02 12:35:07 -070077import android.text.BidiFormatter;
78import android.text.TextDirectionHeuristics;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070079import android.text.TextUtils;
80import android.util.Log;
Paul Soulos2a4207f2014-07-31 17:09:05 -070081import android.view.ContextMenu;
82import android.view.ContextMenu.ContextMenuInfo;
Brian Attwelld28851f2014-06-10 13:25:07 -070083import android.view.Menu;
Brian Attwelld28851f2014-06-10 13:25:07 -070084import android.view.MenuInflater;
Paul Souloseb64a4b2014-07-07 17:03:27 -070085import android.view.MenuItem;
Nancy Chenf619e432014-08-18 20:15:12 -070086import android.view.MotionEvent;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070087import android.view.View;
88import android.view.View.OnClickListener;
Paul Soulos2a4207f2014-07-31 17:09:05 -070089import android.view.View.OnCreateContextMenuListener;
Daisuke Miyakawa10d7df72011-07-29 16:11:05 -070090import android.view.WindowManager;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070091import android.widget.Toast;
Brian Attwelld28851f2014-06-10 13:25:07 -070092import android.widget.Toolbar;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070093
Christine Chen72b3ab12013-08-13 23:22:34 +020094import com.android.contacts.ContactSaveService;
Brian Attwelld28851f2014-06-10 13:25:07 -070095import com.android.contacts.ContactsActivity;
Brian Attwell2d150da2014-07-09 22:35:56 -070096import com.android.contacts.NfcHandler;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070097import com.android.contacts.R;
Paul Souloseb64a4b2014-07-07 17:03:27 -070098import com.android.contacts.common.CallUtil;
Paul Soulos2a4207f2014-07-31 17:09:05 -070099import com.android.contacts.common.ClipboardUtils;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700100import com.android.contacts.common.Collapser;
101import com.android.contacts.common.ContactsUtils;
Brian Attwelld28851f2014-06-10 13:25:07 -0700102import com.android.contacts.common.editor.SelectAccountDialogFragment;
Nancy Chen70e3f4f2014-08-13 16:04:43 -0700103import com.android.contacts.common.interactions.TouchPointManager;
Brian Attwell31b2d422014-06-05 00:14:58 -0700104import com.android.contacts.common.lettertiles.LetterTileDrawable;
Brian Attwell752cccf2014-06-10 16:24:04 -0700105import com.android.contacts.common.list.ShortcutIntentBuilder;
106import com.android.contacts.common.list.ShortcutIntentBuilder.OnShortcutIntentCreatedListener;
Chiao Cheng0d5588d2012-11-26 15:34:14 -0800107import com.android.contacts.common.model.AccountTypeManager;
Yorke Leecd321f62013-10-28 15:20:15 -0700108import com.android.contacts.common.model.Contact;
109import com.android.contacts.common.model.ContactLoader;
110import com.android.contacts.common.model.RawContact;
Chiao Cheng428f0082012-11-13 18:38:56 -0800111import com.android.contacts.common.model.account.AccountType;
Brian Attwelld28851f2014-06-10 13:25:07 -0700112import com.android.contacts.common.model.account.AccountWithDataSet;
Yorke Leecd321f62013-10-28 15:20:15 -0700113import com.android.contacts.common.model.dataitem.DataItem;
Chiao Cheng428f0082012-11-13 18:38:56 -0800114import com.android.contacts.common.model.dataitem.DataKind;
Yorke Leecd321f62013-10-28 15:20:15 -0700115import com.android.contacts.common.model.dataitem.EmailDataItem;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700116import com.android.contacts.common.model.dataitem.EventDataItem;
Yorke Leecd321f62013-10-28 15:20:15 -0700117import com.android.contacts.common.model.dataitem.ImDataItem;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700118import com.android.contacts.common.model.dataitem.NicknameDataItem;
119import com.android.contacts.common.model.dataitem.NoteDataItem;
120import com.android.contacts.common.model.dataitem.OrganizationDataItem;
Paul Soulosb3054e52014-06-05 16:46:02 -0700121import com.android.contacts.common.model.dataitem.PhoneDataItem;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700122import com.android.contacts.common.model.dataitem.RelationDataItem;
123import com.android.contacts.common.model.dataitem.SipAddressDataItem;
124import com.android.contacts.common.model.dataitem.StructuredNameDataItem;
125import com.android.contacts.common.model.dataitem.StructuredPostalDataItem;
126import com.android.contacts.common.model.dataitem.WebsiteDataItem;
127import com.android.contacts.common.util.DateUtils;
Brian Attwellfaf97392014-07-10 18:32:30 -0700128import com.android.contacts.common.util.MaterialColorMapUtils;
129import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
Brian Attwelld41ab8a2014-08-07 11:08:55 -0700130import com.android.contacts.common.util.ViewUtil;
Paul Soulos333091a2014-07-22 13:54:41 -0700131import com.android.contacts.detail.ContactDisplayUtils;
Paul Soulosf19dda92014-09-15 15:48:02 -0700132import com.android.contacts.editor.ContactEditorFragment;
Paul Soulos899aa212014-06-11 12:04:43 -0700133import com.android.contacts.interactions.CalendarInteractionsLoader;
Paul Soulosab840442014-06-17 14:08:40 -0700134import com.android.contacts.interactions.CallLogInteractionsLoader;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700135import com.android.contacts.interactions.ContactDeletionInteraction;
Paul Soulosb3054e52014-06-05 16:46:02 -0700136import com.android.contacts.interactions.ContactInteraction;
Paul Soulosb3054e52014-06-05 16:46:02 -0700137import com.android.contacts.interactions.SmsInteractionsLoader;
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700138import com.android.contacts.quickcontact.ExpandingEntryCardView.Entry;
Paul Soulos2a4207f2014-07-31 17:09:05 -0700139import com.android.contacts.quickcontact.ExpandingEntryCardView.EntryContextMenuInfo;
Paul Soulos2ed2a732014-08-12 11:58:39 -0700140import com.android.contacts.quickcontact.ExpandingEntryCardView.EntryTag;
Brian Attwelle8ce6ee2014-06-27 18:26:32 -0700141import com.android.contacts.quickcontact.ExpandingEntryCardView.ExpandingEntryCardViewListener;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -0700142import com.android.contacts.util.ImageViewDrawableSetter;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700143import com.android.contacts.util.PhoneCapabilityTester;
Brian Attwellb7e43642014-06-02 14:33:04 -0700144import com.android.contacts.util.SchedulingUtils;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700145import com.android.contacts.util.StructuredPostalUtils;
Brian Attwellb7e43642014-06-02 14:33:04 -0700146import com.android.contacts.widget.MultiShrinkScroller;
147import com.android.contacts.widget.MultiShrinkScroller.MultiShrinkScrollerListener;
Brian Attwell02eaf112014-08-27 21:03:42 -0700148import com.android.contacts.widget.QuickContactImageView;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -0700149import com.google.common.base.Preconditions;
150import com.google.common.collect.Lists;
151
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700152import java.util.ArrayList;
Paul Soulos899aa212014-06-11 12:04:43 -0700153import java.util.Arrays;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700154import java.util.Calendar;
Paul Soulosb3054e52014-06-05 16:46:02 -0700155import java.util.Collections;
156import java.util.Comparator;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700157import java.util.Date;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700158import java.util.HashMap;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700159import java.util.List;
Paul Soulosb3054e52014-06-05 16:46:02 -0700160import java.util.Map;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700161
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700162/**
163 * Mostly translucent {@link Activity} that shows QuickContact dialog. It loads
164 * data asynchronously, and then shows a popup with details centered around
165 * {@link Intent#getSourceBounds()}.
166 */
Brian Attwelld28851f2014-06-10 13:25:07 -0700167public class QuickContactActivity extends ContactsActivity {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700168
169 /**
170 * QuickContacts immediately takes up the full screen. All possible information is shown.
171 * This value for {@link android.provider.ContactsContract.QuickContact#EXTRA_MODE}
172 * should only be used by the Contacts app.
173 */
174 public static final int MODE_FULLY_EXPANDED = 4;
175
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700176 private static final String TAG = "QuickContact";
177
Brian Attwell9b889e62014-06-23 18:25:32 -0700178 private static final String KEY_THEME_COLOR = "theme_color";
179
Brian Attwell9b889e62014-06-23 18:25:32 -0700180 private static final int ANIMATION_STATUS_BAR_COLOR_CHANGE_DURATION = 150;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700181 private static final int REQUEST_CODE_CONTACT_EDITOR_ACTIVITY = 1;
Paul Soulos2e6387f2014-09-10 15:40:26 -0700182 private static final int DEFAULT_SCRIM_ALPHA = 0xC8;
183 private static final int SCRIM_COLOR = Color.argb(DEFAULT_SCRIM_ALPHA, 0, 0, 0);
Paul Soulosab54ea12014-08-28 17:22:38 -0700184 private static final int REQUEST_CODE_CONTACT_SELECTION_ACTIVITY = 2;
Paul Soulosea5e0b72014-07-08 18:09:44 -0700185 private static final String MIMETYPE_SMS = "vnd.android-dir/mms-sms";
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700186
Brian Attwell752cccf2014-06-10 16:24:04 -0700187 /** This is the Intent action to install a shortcut in the launcher. */
188 private static final String ACTION_INSTALL_SHORTCUT =
189 "com.android.launcher.action.INSTALL_SHORTCUT";
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700190
191 @SuppressWarnings("deprecation")
192 private static final String LEGACY_AUTHORITY = android.provider.Contacts.AUTHORITY;
193
Paul Soulose0055662014-07-10 16:33:08 -0700194 private static final String MIMETYPE_GPLUS_PROFILE =
195 "vnd.android.cursor.item/vnd.googleplus.profile";
196 private static final String INTENT_DATA_GPLUS_PROFILE_ADD_TO_CIRCLE = "Add to circle";
197 private static final String MIMETYPE_HANGOUTS =
198 "vnd.android.cursor.item/vnd.googleplus.profile.comm";
199 private static final String INTENT_DATA_HANGOUTS_VIDEO = "Start video call";
Paul Soulos48fc9122014-08-26 13:52:36 -0700200 private static final String CALL_ORIGIN_QUICK_CONTACTS_ACTIVITY =
201 "com.android.contacts.quickcontact.QuickContactActivity";
Paul Soulose0055662014-07-10 16:33:08 -0700202
Brian Attwell63176c92014-08-18 15:14:18 -0700203 /**
204 * The URI used to load the the Contact. Once the contact is loaded, use Contact#getLookupUri()
205 * instead of referencing this URI.
206 */
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700207 private Uri mLookupUri;
208 private String[] mExcludeMimes;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700209 private int mExtraMode;
210 private int mStatusBarColor;
211 private boolean mHasAlreadyBeenOpened;
Paul Soulos97f27802014-09-08 13:55:45 -0700212 private boolean mOnlyOnePhoneNumber;
213 private boolean mOnlyOneEmail;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700214
Brian Attwell02eaf112014-08-27 21:03:42 -0700215 private QuickContactImageView mPhotoView;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700216 private ExpandingEntryCardView mContactCard;
Brian Attwell60953692014-07-11 17:18:46 -0700217 private ExpandingEntryCardView mNoContactDetailsCard;
Paul Soulosb3054e52014-06-05 16:46:02 -0700218 private ExpandingEntryCardView mRecentCard;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700219 private ExpandingEntryCardView mAboutCard;
Brian Attwellb7e43642014-06-02 14:33:04 -0700220 private MultiShrinkScroller mScroller;
Brian Attwelld28851f2014-06-10 13:25:07 -0700221 private SelectAccountDialogFragmentListener mSelectAccountFragmentListener;
Brian Attwell6bb01342014-08-20 23:16:00 -0700222 private AsyncTask<Void, Void, Cp2DataCardModel> mEntriesAndActionsTask;
Brian Attwell30e1ef12014-09-02 15:49:20 -0700223 private AsyncTask<Void, Void, Void> mRecentDataTask;
Brian Attwell6bb01342014-08-20 23:16:00 -0700224 /**
225 * The last copy of Cp2DataCardModel that was passed to {@link #populateContactAndAboutCard}.
226 */
227 private Cp2DataCardModel mCachedCp2DataCardModel;
Brian Attwellf1076dc2014-08-12 21:28:21 -0700228 /**
229 * This scrim's opacity is controlled in two different ways. 1) Before the initial entrance
230 * animation finishes, the opacity is animated by a value animator. This is designed to
231 * distract the user from the length of the initial loading time. 2) After the initial
232 * entrance animation, the opacity is directly related to scroll position.
233 */
Brian Attwell9b889e62014-06-23 18:25:32 -0700234 private ColorDrawable mWindowScrim;
Brian Attwellf1076dc2014-08-12 21:28:21 -0700235 private boolean mIsEntranceAnimationFinished;
Brian Attwella3859ed2014-07-15 13:51:55 -0700236 private MaterialColorMapUtils mMaterialColorMapUtils;
Brian Attwell8477eaf2014-06-18 15:39:50 -0700237 private boolean mIsExitAnimationInProgress;
Brian Attwell8571dd32014-06-23 23:29:10 -0700238 private boolean mHasComputedThemeColor;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700239
Paul Soulosa42ef762014-08-20 10:26:10 -0700240 /**
241 * Used to stop the ExpandingEntry cards from adjusting between an entry click and the intent
242 * being launched.
243 */
244 private boolean mHasIntentLaunched;
245
Yorke Lee6cb6f6f2013-09-26 16:26:03 -0700246 private Contact mContactData;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700247 private ContactLoader mContactLoader;
Brian Attwell9b889e62014-06-23 18:25:32 -0700248 private PorterDuffColorFilter mColorFilter;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700249
Josh Gargus9758a922012-03-08 17:12:42 -0800250 private final ImageViewDrawableSetter mPhotoSetter = new ImageViewDrawableSetter();
251
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700252 /**
Paul Souloscb4fcc72014-07-15 14:08:50 -0700253 * {@link #LEADING_MIMETYPES} is used to sort MIME-types.
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700254 *
255 * <p>The MIME-types in {@link #LEADING_MIMETYPES} appear in the front of the dialog,
256 * in the order specified here.</p>
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700257 */
258 private static final List<String> LEADING_MIMETYPES = Lists.newArrayList(
Paul Souloscb4fcc72014-07-15 14:08:50 -0700259 Phone.CONTENT_ITEM_TYPE, SipAddress.CONTENT_ITEM_TYPE, Email.CONTENT_ITEM_TYPE,
Paul Soulos405ae402014-07-14 16:16:36 -0700260 StructuredPostal.CONTENT_ITEM_TYPE);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700261
Paul Soulosa153dba2014-08-06 13:44:57 -0700262 private static final List<String> SORTED_ABOUT_CARD_MIMETYPES = Lists.newArrayList(
263 Nickname.CONTENT_ITEM_TYPE,
264 // Phonetic name is inserted after nickname if it is available.
265 // No mimetype for phonetic name exists.
266 Website.CONTENT_ITEM_TYPE,
267 Organization.CONTENT_ITEM_TYPE,
268 Event.CONTENT_ITEM_TYPE,
269 Relation.CONTENT_ITEM_TYPE,
270 Im.CONTENT_ITEM_TYPE,
271 GroupMembership.CONTENT_ITEM_TYPE,
272 Identity.CONTENT_ITEM_TYPE,
273 Note.CONTENT_ITEM_TYPE);
Paul Souloseb64a4b2014-07-07 17:03:27 -0700274
Brian Attwellc62cc792014-10-02 12:35:07 -0700275 private static final BidiFormatter sBidiFormatter = BidiFormatter.getInstance();
276
Paul Soulosb3054e52014-06-05 16:46:02 -0700277 /** Id for the background contact loader */
278 private static final int LOADER_CONTACT_ID = 0;
279
Paul Soulosab840442014-06-17 14:08:40 -0700280 private static final String KEY_LOADER_EXTRA_PHONES =
281 QuickContactActivity.class.getCanonicalName() + ".KEY_LOADER_EXTRA_PHONES";
282
Paul Soulosb3054e52014-06-05 16:46:02 -0700283 /** Id for the background Sms Loader */
284 private static final int LOADER_SMS_ID = 1;
Paul Soulosb3054e52014-06-05 16:46:02 -0700285 private static final int MAX_SMS_RETRIEVE = 3;
Paul Soulosab840442014-06-17 14:08:40 -0700286
287 /** Id for the back Calendar Loader */
Paul Soulos899aa212014-06-11 12:04:43 -0700288 private static final int LOADER_CALENDAR_ID = 2;
Paul Soulosab840442014-06-17 14:08:40 -0700289 private static final String KEY_LOADER_EXTRA_EMAILS =
290 QuickContactActivity.class.getCanonicalName() + ".KEY_LOADER_EXTRA_EMAILS";
Paul Soulos899aa212014-06-11 12:04:43 -0700291 private static final int MAX_PAST_CALENDAR_RETRIEVE = 3;
292 private static final int MAX_FUTURE_CALENDAR_RETRIEVE = 3;
293 private static final long PAST_MILLISECOND_TO_SEARCH_LOCAL_CALENDAR =
Paul Soulos40d7a652014-09-03 13:54:11 -0700294 1L * 24L * 60L * 60L * 1000L /* 1 day */;
Paul Soulos899aa212014-06-11 12:04:43 -0700295 private static final long FUTURE_MILLISECOND_TO_SEARCH_LOCAL_CALENDAR =
Paul Soulos40d7a652014-09-03 13:54:11 -0700296 7L * 24L * 60L * 60L * 1000L /* 7 days */;
Paul Soulosb3054e52014-06-05 16:46:02 -0700297
Paul Soulosab840442014-06-17 14:08:40 -0700298 /** Id for the background Call Log Loader */
299 private static final int LOADER_CALL_LOG_ID = 3;
300 private static final int MAX_CALL_LOG_RETRIEVE = 3;
Brian Attwell60953692014-07-11 17:18:46 -0700301 private static final int MIN_NUM_CONTACT_ENTRIES_SHOWN = 3;
302 private static final int MIN_NUM_COLLAPSED_RECENT_ENTRIES_SHOWN = 3;
303 private static final int CARD_ENTRY_ID_EDIT_CONTACT = -2;
Paul Soulosab840442014-06-17 14:08:40 -0700304
305
306 private static final int[] mRecentLoaderIds = new int[]{
307 LOADER_SMS_ID,
308 LOADER_CALENDAR_ID,
309 LOADER_CALL_LOG_ID};
Paul Soulosa8fce962014-07-15 13:37:24 -0700310 private Map<Integer, List<ContactInteraction>> mRecentLoaderResults = new HashMap<>();
Paul Soulosb3054e52014-06-05 16:46:02 -0700311
Brian Attwelld28851f2014-06-10 13:25:07 -0700312 private static final String FRAGMENT_TAG_SELECT_ACCOUNT = "select_account_fragment";
Yorke Lee6cb6f6f2013-09-26 16:26:03 -0700313
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700314 final OnClickListener mEntryClickHandler = new OnClickListener() {
315 @Override
316 public void onClick(View v) {
Paul Soulos2ed2a732014-08-12 11:58:39 -0700317 final Object entryTagObject = v.getTag();
318 if (entryTagObject == null || !(entryTagObject instanceof EntryTag)) {
319 Log.w(TAG, "EntryTag was not used correctly");
320 return;
321 }
322 final EntryTag entryTag = (EntryTag) entryTagObject;
323 final Intent intent = entryTag.getIntent();
324 final int dataId = entryTag.getId();
325
Brian Attwell60953692014-07-11 17:18:46 -0700326 if (dataId == CARD_ENTRY_ID_EDIT_CONTACT) {
327 editContact();
328 return;
329 }
Paul Soulosea5e0b72014-07-08 18:09:44 -0700330
331 // Default to USAGE_TYPE_CALL. Usage is summed among all types for sorting each data id
332 // so the exact usage type is not necessary in all cases
333 String usageType = DataUsageFeedback.USAGE_TYPE_CALL;
334
Paul Soulos20bdf9d2014-07-28 14:31:54 -0700335 final Uri intentUri = intent.getData();
336 if ((intentUri != null && intentUri.getScheme() != null &&
Jay Shrauner1cd88e32014-09-05 15:37:55 -0700337 intentUri.getScheme().equals(ContactsUtils.SCHEME_SMSTO)) ||
Paul Soulosea5e0b72014-07-08 18:09:44 -0700338 (intent.getType() != null && intent.getType().equals(MIMETYPE_SMS))) {
339 usageType = DataUsageFeedback.USAGE_TYPE_SHORT_TEXT;
340 }
341
342 // Data IDs start at 1 so anything less is invalid
343 if (dataId > 0) {
Paul Soulos20bdf9d2014-07-28 14:31:54 -0700344 final Uri dataUsageUri = DataUsageFeedback.FEEDBACK_URI.buildUpon()
Paul Soulosea5e0b72014-07-08 18:09:44 -0700345 .appendPath(String.valueOf(dataId))
346 .appendQueryParameter(DataUsageFeedback.USAGE_TYPE, usageType)
347 .build();
348 final boolean successful = getContentResolver().update(
Paul Soulos20bdf9d2014-07-28 14:31:54 -0700349 dataUsageUri, new ContentValues(), null, null) > 0;
Paul Soulosea5e0b72014-07-08 18:09:44 -0700350 if (!successful) {
351 Log.w(TAG, "DataUsageFeedback increment failed");
352 }
353 } else {
354 Log.w(TAG, "Invalid Data ID");
355 }
356
Nancy Chen70e3f4f2014-08-13 16:04:43 -0700357 // Pass the touch point through the intent for use in the InCallUI
358 if (Intent.ACTION_CALL.equals(intent.getAction())) {
Nancy Chen1f8a3fd2014-08-20 10:59:25 -0700359 if (TouchPointManager.getInstance().hasValidPoint()) {
Nancy Chen70e3f4f2014-08-13 16:04:43 -0700360 Bundle extras = new Bundle();
Nancy Chen1f8a3fd2014-08-20 10:59:25 -0700361 extras.putParcelable(TouchPointManager.TOUCH_POINT,
362 TouchPointManager.getInstance().getPoint());
Tyler Gunn03192222014-09-10 15:20:09 -0700363 intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
Nancy Chen70e3f4f2014-08-13 16:04:43 -0700364 }
365 }
366
Paul Soulos793e73c2014-09-09 16:58:24 -0700367 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
368
Paul Soulos2e6387f2014-09-10 15:40:26 -0700369 // Force the window dim amount to the scrim value for app transition animations
370 // The scrim may be removed before the window transitions to the new activity, which
371 // can cause a flicker in the status and navigation bar. Set dim alone does not work
372 // well because the request is passed through IPC which makes it slow to animate.
373 getWindow().setFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND,
374 WindowManager.LayoutParams.FLAG_DIM_BEHIND);
375 getWindow().setDimAmount(mWindowScrim.getAlpha() / DEFAULT_SCRIM_ALPHA);
376
Paul Soulosa42ef762014-08-20 10:26:10 -0700377 mHasIntentLaunched = true;
Paul Soulosea5e0b72014-07-08 18:09:44 -0700378 startActivity(intent);
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700379 }
380 };
381
Brian Attwelle8ce6ee2014-06-27 18:26:32 -0700382 final ExpandingEntryCardViewListener mExpandingEntryCardViewListener
383 = new ExpandingEntryCardViewListener() {
384 @Override
385 public void onCollapse(int heightDelta) {
386 mScroller.prepareForShrinkingScrollChild(heightDelta);
387 }
Paul Soulos0cda9ae2014-07-23 11:27:28 -0700388
389 @Override
390 public void onExpand(int heightDelta) {
391 mScroller.prepareForExpandingScrollChild();
392 }
Brian Attwelle8ce6ee2014-06-27 18:26:32 -0700393 };
394
Paul Soulos97f27802014-09-08 13:55:45 -0700395 private interface ContextMenuIds {
396 static final int COPY_TEXT = 0;
397 static final int CLEAR_DEFAULT = 1;
398 static final int SET_DEFAULT = 2;
399 }
400
Paul Soulos2a4207f2014-07-31 17:09:05 -0700401 private final OnCreateContextMenuListener mEntryContextMenuListener =
402 new OnCreateContextMenuListener() {
403 @Override
404 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
405 if (menuInfo == null) {
406 return;
407 }
Paul Soulos97f27802014-09-08 13:55:45 -0700408 final EntryContextMenuInfo info = (EntryContextMenuInfo) menuInfo;
Paul Soulos2a4207f2014-07-31 17:09:05 -0700409 menu.setHeaderTitle(info.getCopyText());
Paul Soulos97f27802014-09-08 13:55:45 -0700410 menu.add(ContextMenu.NONE, ContextMenuIds.COPY_TEXT,
411 ContextMenu.NONE, getString(R.string.copy_text));
412
413 // Don't allow setting or clearing of defaults for non-editable contacts
414 if (!isContactEditable()) {
415 return;
416 }
417
418 final String selectedMimeType = info.getMimeType();
419
420 // Defaults to true will only enable the detail to be copied to the clipboard.
421 boolean onlyOneOfMimeType = true;
422
423 // Only allow primary support for Phone and Email content types
424 if (Phone.CONTENT_ITEM_TYPE.equals(selectedMimeType)) {
425 onlyOneOfMimeType = mOnlyOnePhoneNumber;
426 } else if (Email.CONTENT_ITEM_TYPE.equals(selectedMimeType)) {
427 onlyOneOfMimeType = mOnlyOneEmail;
428 }
429
430 // Checking for previously set default
431 if (info.isSuperPrimary()) {
432 menu.add(ContextMenu.NONE, ContextMenuIds.CLEAR_DEFAULT,
433 ContextMenu.NONE, getString(R.string.clear_default));
434 } else if (!onlyOneOfMimeType) {
435 menu.add(ContextMenu.NONE, ContextMenuIds.SET_DEFAULT,
436 ContextMenu.NONE, getString(R.string.set_default));
437 }
Paul Soulos2a4207f2014-07-31 17:09:05 -0700438 }
439 };
440
441 @Override
442 public boolean onContextItemSelected(MenuItem item) {
443 EntryContextMenuInfo menuInfo;
444 try {
445 menuInfo = (EntryContextMenuInfo) item.getMenuInfo();
446 } catch (ClassCastException e) {
447 Log.e(TAG, "bad menuInfo", e);
448 return false;
449 }
450
Paul Soulos97f27802014-09-08 13:55:45 -0700451 switch (item.getItemId()) {
452 case ContextMenuIds.COPY_TEXT:
453 ClipboardUtils.copyText(this, menuInfo.getCopyLabel(), menuInfo.getCopyText(),
454 true);
455 return true;
456 case ContextMenuIds.SET_DEFAULT:
457 final Intent setIntent = ContactSaveService.createSetSuperPrimaryIntent(this,
458 menuInfo.getId());
459 this.startService(setIntent);
460 return true;
461 case ContextMenuIds.CLEAR_DEFAULT:
462 final Intent clearIntent = ContactSaveService.createClearPrimaryIntent(this,
463 menuInfo.getId());
464 this.startService(clearIntent);
465 return true;
466 default:
467 throw new IllegalArgumentException("Unknown menu option " + item.getItemId());
468 }
Paul Soulos2a4207f2014-07-31 17:09:05 -0700469 }
470
Brian Attwelld28851f2014-06-10 13:25:07 -0700471 /**
472 * Headless fragment used to handle account selection callbacks invoked from
473 * {@link DirectoryContactUtil}.
474 */
475 public static class SelectAccountDialogFragmentListener extends Fragment
476 implements SelectAccountDialogFragment.Listener {
477
478 private QuickContactActivity mQuickContactActivity;
479
480 public SelectAccountDialogFragmentListener() {}
481
482 @Override
483 public void onAccountChosen(AccountWithDataSet account, Bundle extraArgs) {
484 DirectoryContactUtil.createCopy(mQuickContactActivity.mContactData.getContentValues(),
485 account, mQuickContactActivity);
486 }
487
488 @Override
489 public void onAccountSelectorCancelled() {}
490
491 /**
492 * Set the parent activity. Since rotation can cause this fragment to be used across
493 * more than one activity instance, we need to explicitly set this value instead
494 * of making this class non-static.
495 */
496 public void setQuickContactActivity(QuickContactActivity quickContactActivity) {
497 mQuickContactActivity = quickContactActivity;
498 }
499 }
500
Brian Attwellb7e43642014-06-02 14:33:04 -0700501 final MultiShrinkScrollerListener mMultiShrinkScrollerListener
502 = new MultiShrinkScrollerListener() {
503 @Override
504 public void onScrolledOffBottom() {
Brian Attwellf1076dc2014-08-12 21:28:21 -0700505 finish();
Brian Attwellb7e43642014-06-02 14:33:04 -0700506 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700507
508 @Override
509 public void onEnterFullscreen() {
510 updateStatusBarColor();
511 }
512
513 @Override
514 public void onExitFullscreen() {
515 updateStatusBarColor();
516 }
Brian Attwell8477eaf2014-06-18 15:39:50 -0700517
518 @Override
519 public void onStartScrollOffBottom() {
Brian Attwell8477eaf2014-06-18 15:39:50 -0700520 mIsExitAnimationInProgress = true;
521 }
Brian Attwell8477eaf2014-06-18 15:39:50 -0700522
Brian Attwell8477eaf2014-06-18 15:39:50 -0700523 @Override
Brian Attwellf1076dc2014-08-12 21:28:21 -0700524 public void onEntranceAnimationDone() {
525 mIsEntranceAnimationFinished = true;
526 }
527
528 @Override
529 public void onTransparentViewHeightChange(float ratio) {
530 if (mIsEntranceAnimationFinished) {
531 mWindowScrim.setAlpha((int) (0xFF * ratio));
Brian Attwell8477eaf2014-06-18 15:39:50 -0700532 }
Brian Attwell8477eaf2014-06-18 15:39:50 -0700533 }
Brian Attwellb7e43642014-06-02 14:33:04 -0700534 };
535
Paul Souloseb64a4b2014-07-07 17:03:27 -0700536
537 /**
538 * Data items are compared to the same mimetype based off of three qualities:
539 * 1. Super primary
540 * 2. Primary
541 * 3. Times used
542 */
543 private final Comparator<DataItem> mWithinMimeTypeDataItemComparator =
544 new Comparator<DataItem>() {
545 @Override
546 public int compare(DataItem lhs, DataItem rhs) {
547 if (!lhs.getMimeType().equals(rhs.getMimeType())) {
548 Log.wtf(TAG, "Comparing DataItems with different mimetypes lhs.getMimeType(): " +
549 lhs.getMimeType() + " rhs.getMimeType(): " + rhs.getMimeType());
550 return 0;
551 }
552
553 if (lhs.isSuperPrimary()) {
554 return -1;
555 } else if (rhs.isSuperPrimary()) {
556 return 1;
557 } else if (lhs.isPrimary() && !rhs.isPrimary()) {
558 return -1;
559 } else if (!lhs.isPrimary() && rhs.isPrimary()) {
560 return 1;
561 } else {
562 final int lhsTimesUsed =
563 lhs.getTimesUsed() == null ? 0 : lhs.getTimesUsed();
564 final int rhsTimesUsed =
565 rhs.getTimesUsed() == null ? 0 : rhs.getTimesUsed();
566
567 return rhsTimesUsed - lhsTimesUsed;
568 }
569 }
570 };
571
Paul Souloscb4fcc72014-07-15 14:08:50 -0700572 /**
573 * Sorts among different mimetypes based off:
574 * 1. Times used
575 * 2. Last time used
576 * 3. Statically defined
577 */
Paul Souloseb64a4b2014-07-07 17:03:27 -0700578 private final Comparator<List<DataItem>> mAmongstMimeTypeDataItemComparator =
579 new Comparator<List<DataItem>> () {
580 @Override
581 public int compare(List<DataItem> lhsList, List<DataItem> rhsList) {
582 DataItem lhs = lhsList.get(0);
583 DataItem rhs = rhsList.get(0);
584 final int lhsTimesUsed = lhs.getTimesUsed() == null ? 0 : lhs.getTimesUsed();
585 final int rhsTimesUsed = rhs.getTimesUsed() == null ? 0 : rhs.getTimesUsed();
586 final int timesUsedDifference = rhsTimesUsed - lhsTimesUsed;
587 if (timesUsedDifference != 0) {
588 return timesUsedDifference;
589 }
590
591 final long lhsLastTimeUsed =
592 lhs.getLastTimeUsed() == null ? 0 : lhs.getLastTimeUsed();
593 final long rhsLastTimeUsed =
594 rhs.getLastTimeUsed() == null ? 0 : rhs.getLastTimeUsed();
595 final long lastTimeUsedDifference = rhsLastTimeUsed - lhsLastTimeUsed;
596 if (lastTimeUsedDifference > 0) {
597 return 1;
598 } else if (lastTimeUsedDifference < 0) {
599 return -1;
600 }
601
602 // Times used and last time used are the same. Resort to statically defined.
603 final String lhsMimeType = lhs.getMimeType();
604 final String rhsMimeType = rhs.getMimeType();
605 for (String mimeType : LEADING_MIMETYPES) {
606 if (lhsMimeType.equals(mimeType)) {
607 return -1;
608 } else if (rhsMimeType.equals(mimeType)) {
609 return 1;
610 }
611 }
Paul Souloseb64a4b2014-07-07 17:03:27 -0700612 return 0;
613 }
614 };
615
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700616 @Override
Nancy Chenf619e432014-08-18 20:15:12 -0700617 public boolean dispatchTouchEvent(MotionEvent ev) {
618 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
619 TouchPointManager.getInstance().setPoint((int) ev.getRawX(), (int) ev.getRawY());
620 }
621 return super.dispatchTouchEvent(ev);
622 }
623
624 @Override
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700625 protected void onCreate(Bundle savedInstanceState) {
626 Trace.beginSection("onCreate()");
627 super.onCreate(savedInstanceState);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700628
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700629 getWindow().setStatusBarColor(Color.TRANSPARENT);
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700630
Brian Attwelld28851f2014-06-10 13:25:07 -0700631 processIntent(getIntent());
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700632
Daisuke Miyakawa10d7df72011-07-29 16:11:05 -0700633 // Show QuickContact in front of soft input
634 getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
635 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
636
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700637 setContentView(R.layout.quickcontact_activity);
638
Brian Attwella3859ed2014-07-15 13:51:55 -0700639 mMaterialColorMapUtils = new MaterialColorMapUtils(getResources());
640
Paul Soulos0cda9ae2014-07-23 11:27:28 -0700641 mScroller = (MultiShrinkScroller) findViewById(R.id.multiscroller);
642
Paul Souloseb64a4b2014-07-07 17:03:27 -0700643 mContactCard = (ExpandingEntryCardView) findViewById(R.id.communication_card);
Brian Attwell60953692014-07-11 17:18:46 -0700644 mNoContactDetailsCard = (ExpandingEntryCardView) findViewById(R.id.no_contact_data_card);
Paul Soulosb3054e52014-06-05 16:46:02 -0700645 mRecentCard = (ExpandingEntryCardView) findViewById(R.id.recent_card);
Paul Souloseb64a4b2014-07-07 17:03:27 -0700646 mAboutCard = (ExpandingEntryCardView) findViewById(R.id.about_card);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700647
Brian Attwell60953692014-07-11 17:18:46 -0700648 mNoContactDetailsCard.setOnClickListener(mEntryClickHandler);
Paul Souloseb64a4b2014-07-07 17:03:27 -0700649 mContactCard.setOnClickListener(mEntryClickHandler);
Paul Souloseb64a4b2014-07-07 17:03:27 -0700650 mContactCard.setExpandButtonText(
Paul Soulos23889ba2014-06-10 14:55:32 -0700651 getResources().getString(R.string.expanding_entry_card_view_see_all));
Paul Soulos2a4207f2014-07-31 17:09:05 -0700652 mContactCard.setOnCreateContextMenuListener(mEntryContextMenuListener);
Paul Soulosb3054e52014-06-05 16:46:02 -0700653
654 mRecentCard.setOnClickListener(mEntryClickHandler);
655 mRecentCard.setTitle(getResources().getString(R.string.recent_card_title));
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700656
Paul Souloseb64a4b2014-07-07 17:03:27 -0700657 mAboutCard.setOnClickListener(mEntryClickHandler);
Paul Soulos2a4207f2014-07-31 17:09:05 -0700658 mAboutCard.setOnCreateContextMenuListener(mEntryContextMenuListener);
Paul Souloseb64a4b2014-07-07 17:03:27 -0700659
Brian Attwell02eaf112014-08-27 21:03:42 -0700660 mPhotoView = (QuickContactImageView) findViewById(R.id.photo);
Brian Attwell63176c92014-08-18 15:14:18 -0700661 final View transparentView = findViewById(R.id.transparent_view);
Brian Attwell0d90afe2014-06-18 16:45:41 -0700662 if (mScroller != null) {
Brian Attwell63176c92014-08-18 15:14:18 -0700663 transparentView.setOnClickListener(new OnClickListener() {
Brian Attwell0d90afe2014-06-18 16:45:41 -0700664 @Override
665 public void onClick(View v) {
666 mScroller.scrollOffBottom();
667 }
668 });
669 }
Yorke Lee62196682013-09-06 18:34:17 -0700670
Brian Attwelld41ab8a2014-08-07 11:08:55 -0700671 // Allow a shadow to be shown under the toolbar.
672 ViewUtil.addRectangularOutlineProvider(findViewById(R.id.toolbar_parent), getResources());
673
Brian Attwelld28851f2014-06-10 13:25:07 -0700674 final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
675 setActionBar(toolbar);
Brian Attwell9b889e62014-06-23 18:25:32 -0700676 getActionBar().setTitle(null);
677 // Put a TextView with a known resource id into the ActionBar. This allows us to easily
678 // find the correct TextView location & size later.
679 toolbar.addView(getLayoutInflater().inflate(R.layout.quickcontact_title_placeholder, null));
Yorke Lee62196682013-09-06 18:34:17 -0700680
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700681 mHasAlreadyBeenOpened = savedInstanceState != null;
Brian Attwellf1076dc2014-08-12 21:28:21 -0700682 mIsEntranceAnimationFinished = mHasAlreadyBeenOpened;
Brian Attwell9b889e62014-06-23 18:25:32 -0700683 mWindowScrim = new ColorDrawable(SCRIM_COLOR);
Brian Attwellf1076dc2014-08-12 21:28:21 -0700684 mWindowScrim.setAlpha(0);
Brian Attwell9b889e62014-06-23 18:25:32 -0700685 getWindow().setBackgroundDrawable(mWindowScrim);
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700686
Brian Attwellc33ae172014-07-01 14:21:37 -0700687 mScroller.initialize(mMultiShrinkScrollerListener, mExtraMode == MODE_FULLY_EXPANDED);
Brian Attwellac3ed8e2014-06-27 17:24:42 -0700688 // mScroller needs to perform asynchronous measurements after initalize(), therefore
689 // we can't mark this as GONE.
690 mScroller.setVisibility(View.INVISIBLE);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700691
Brian Attwell9b889e62014-06-23 18:25:32 -0700692 setHeaderNameText(R.string.missing_name);
693
Brian Attwelld28851f2014-06-10 13:25:07 -0700694 mSelectAccountFragmentListener= (SelectAccountDialogFragmentListener) getFragmentManager()
695 .findFragmentByTag(FRAGMENT_TAG_SELECT_ACCOUNT);
696 if (mSelectAccountFragmentListener == null) {
697 mSelectAccountFragmentListener = new SelectAccountDialogFragmentListener();
698 getFragmentManager().beginTransaction().add(0, mSelectAccountFragmentListener,
699 FRAGMENT_TAG_SELECT_ACCOUNT).commit();
700 mSelectAccountFragmentListener.setRetainInstance(true);
701 }
702 mSelectAccountFragmentListener.setQuickContactActivity(this);
Paul Soulos23889ba2014-06-10 14:55:32 -0700703
Brian Attwellf1076dc2014-08-12 21:28:21 -0700704 SchedulingUtils.doOnPreDraw(mScroller, /* drawNextFrame = */ true,
705 new Runnable() {
706 @Override
707 public void run() {
708 if (!mHasAlreadyBeenOpened) {
709 // The initial scrim opacity must match the scrim opacity that would be
710 // achieved by scrolling to the starting position.
711 final float alphaRatio = mExtraMode == MODE_FULLY_EXPANDED ?
712 1 : mScroller.getStartingTransparentHeightRatio();
713 final int duration = getResources().getInteger(
714 android.R.integer.config_shortAnimTime);
715 final int desiredAlpha = (int) (0xFF * alphaRatio);
716 ObjectAnimator o = ObjectAnimator.ofInt(mWindowScrim, "alpha", 0,
717 desiredAlpha).setDuration(duration);
718
719 o.start();
720 }
721 }
722 });
723
Brian Attwell9b889e62014-06-23 18:25:32 -0700724 if (savedInstanceState != null) {
725 final int color = savedInstanceState.getInt(KEY_THEME_COLOR, 0);
Brian Attwellac3ed8e2014-06-27 17:24:42 -0700726 SchedulingUtils.doOnPreDraw(mScroller, /* drawNextFrame = */ false,
727 new Runnable() {
728 @Override
729 public void run() {
730 // Need to wait for the pre draw before setting the initial scroll
731 // value. Prior to pre draw all scroll values are invalid.
732 if (mHasAlreadyBeenOpened) {
733 mScroller.setVisibility(View.VISIBLE);
734 mScroller.setScroll(mScroller.getScrollNeededToBeFullScreen());
735 }
736 // Need to wait for pre draw for setting the theme color. Setting the
737 // header tint before the MultiShrinkScroller has been measured will
738 // cause incorrect tinting calculations.
739 if (color != 0) {
Brian Attwella3859ed2014-07-15 13:51:55 -0700740 setThemeColor(mMaterialColorMapUtils
741 .calculatePrimaryAndSecondaryColor(color));
Brian Attwell9b889e62014-06-23 18:25:32 -0700742 }
Brian Attwellac3ed8e2014-06-27 17:24:42 -0700743 }
744 });
Brian Attwell9b889e62014-06-23 18:25:32 -0700745 }
746
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700747 Trace.endSection();
748 }
749
Paul Soulos405ae402014-07-14 16:16:36 -0700750 @Override
751 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700752 if (requestCode == REQUEST_CODE_CONTACT_EDITOR_ACTIVITY &&
753 resultCode == ContactDeletionInteraction.RESULT_CODE_DELETED) {
754 // The contact that we were showing has been deleted.
755 finish();
Paul Soulosab54ea12014-08-28 17:22:38 -0700756 } else if (requestCode == REQUEST_CODE_CONTACT_SELECTION_ACTIVITY &&
757 resultCode != RESULT_CANCELED) {
758 processIntent(data);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700759 }
760 }
761
762 @Override
763 protected void onNewIntent(Intent intent) {
764 super.onNewIntent(intent);
765 mHasAlreadyBeenOpened = true;
Brian Attwellf1076dc2014-08-12 21:28:21 -0700766 mIsEntranceAnimationFinished = true;
Brian Attwell8571dd32014-06-23 23:29:10 -0700767 mHasComputedThemeColor = false;
Brian Attwelld28851f2014-06-10 13:25:07 -0700768 processIntent(intent);
769 }
770
Brian Attwell9b889e62014-06-23 18:25:32 -0700771 @Override
772 public void onSaveInstanceState(Bundle savedInstanceState) {
773 super.onSaveInstanceState(savedInstanceState);
774 if (mColorFilter != null) {
775 savedInstanceState.putInt(KEY_THEME_COLOR, mColorFilter.getColor());
776 }
777 }
778
Brian Attwelld28851f2014-06-10 13:25:07 -0700779 private void processIntent(Intent intent) {
Paul Soulosf19dda92014-09-15 15:48:02 -0700780 if (intent == null) {
781 finish();
782 return;
783 }
Brian Attwelld28851f2014-06-10 13:25:07 -0700784 Uri lookupUri = intent.getData();
785
786 // Check to see whether it comes from the old version.
787 if (lookupUri != null && LEGACY_AUTHORITY.equals(lookupUri.getAuthority())) {
788 final long rawContactId = ContentUris.parseId(lookupUri);
789 lookupUri = RawContacts.getContactLookupUri(getContentResolver(),
790 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
791 }
792 mExtraMode = getIntent().getIntExtra(QuickContact.EXTRA_MODE,
793 QuickContact.MODE_LARGE);
794 final Uri oldLookupUri = mLookupUri;
795
796 mLookupUri = Preconditions.checkNotNull(lookupUri, "missing lookupUri");
797 mExcludeMimes = intent.getStringArrayExtra(QuickContact.EXTRA_EXCLUDE_MIMES);
798 if (oldLookupUri == null) {
799 mContactLoader = (ContactLoader) getLoaderManager().initLoader(
800 LOADER_CONTACT_ID, null, mLoaderContactCallbacks);
801 } else if (oldLookupUri != mLookupUri) {
802 // After copying a directory contact, the contact URI changes. Therefore,
803 // we need to restart the loader and reload the new contact.
Paul Soulos899aa212014-06-11 12:04:43 -0700804 for (int interactionLoaderId : mRecentLoaderIds) {
805 getLoaderManager().destroyLoader(interactionLoaderId);
806 }
Paul Soulos405ae402014-07-14 16:16:36 -0700807 mContactLoader = (ContactLoader) getLoaderManager().restartLoader(
808 LOADER_CONTACT_ID, null, mLoaderContactCallbacks);
Brian Attwelld28851f2014-06-10 13:25:07 -0700809 }
Brian Attwell2d150da2014-07-09 22:35:56 -0700810
811 NfcHandler.register(this, mLookupUri);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700812 }
813
Brian Attwellb7e43642014-06-02 14:33:04 -0700814 private void runEntranceAnimation() {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700815 if (mHasAlreadyBeenOpened) {
816 return;
817 }
818 mHasAlreadyBeenOpened = true;
Brian Attwellc33ae172014-07-01 14:21:37 -0700819 mScroller.scrollUpForEntranceAnimation(mExtraMode != MODE_FULLY_EXPANDED);
Brian Attwellb7e43642014-06-02 14:33:04 -0700820 }
821
Katherine Kuan81281ee2011-07-28 16:20:59 -0700822 /** Assign this string to the view if it is not empty. */
Brian Attwelld28851f2014-06-10 13:25:07 -0700823 private void setHeaderNameText(int resId) {
Brian Attwell9b889e62014-06-23 18:25:32 -0700824 if (mScroller != null) {
Brian Attwellf43f5732014-06-30 11:18:20 -0700825 mScroller.setTitle(getText(resId) == null ? null : getText(resId).toString());
Brian Attwell9b889e62014-06-23 18:25:32 -0700826 }
Katherine Kuan81281ee2011-07-28 16:20:59 -0700827 }
828
829 /** Assign this string to the view if it is not empty. */
Brian Attwellf43f5732014-06-30 11:18:20 -0700830 private void setHeaderNameText(String value) {
Brian Attwelld28851f2014-06-10 13:25:07 -0700831 if (!TextUtils.isEmpty(value)) {
Brian Attwell9b889e62014-06-23 18:25:32 -0700832 if (mScroller != null) {
Brian Attwellf43f5732014-06-30 11:18:20 -0700833 mScroller.setTitle(value);
Brian Attwell9b889e62014-06-23 18:25:32 -0700834 }
Katherine Kuan81281ee2011-07-28 16:20:59 -0700835 }
836 }
837
838 /**
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700839 * Check if the given MIME-type appears in the list of excluded MIME-types
840 * that the most-recent caller requested.
841 */
842 private boolean isMimeExcluded(String mimeType) {
843 if (mExcludeMimes == null) return false;
844 for (String excludedMime : mExcludeMimes) {
845 if (TextUtils.equals(excludedMime, mimeType)) {
846 return true;
847 }
848 }
849 return false;
850 }
851
852 /**
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200853 * Handle the result from the ContactLoader
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700854 */
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700855 private void bindContactData(final Contact data) {
856 Trace.beginSection("bindContactData");
Yorke Lee6cb6f6f2013-09-26 16:26:03 -0700857 mContactData = data;
Brian Attwelld28851f2014-06-10 13:25:07 -0700858 invalidateOptionsMenu();
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700859
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700860 Trace.endSection();
861 Trace.beginSection("Set display photo & name");
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700862
Brian Attwell02eaf112014-08-27 21:03:42 -0700863 mPhotoView.setIsBusiness(mContactData.isDisplayNameFromOrganization());
Yorke Lee62196682013-09-06 18:34:17 -0700864 mPhotoSetter.setupContactPhoto(data, mPhotoView);
Brian Attwell31b2d422014-06-05 00:14:58 -0700865 extractAndApplyTintFromPhotoViewAsynchronously();
Brian Attwell4936a922014-07-02 16:16:59 -0700866 analyzeWhitenessOfPhotoAsynchronously();
Paul Soulos333091a2014-07-22 13:54:41 -0700867 setHeaderNameText(ContactDisplayUtils.getDisplayName(this, data).toString());
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700868
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700869 Trace.endSection();
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700870
Brian Attwell6bb01342014-08-20 23:16:00 -0700871 mEntriesAndActionsTask = new AsyncTask<Void, Void, Cp2DataCardModel>() {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700872
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700873 @Override
Brian Attwell6bb01342014-08-20 23:16:00 -0700874 protected Cp2DataCardModel doInBackground(
Paul Souloseb64a4b2014-07-07 17:03:27 -0700875 Void... params) {
876 return generateDataModelFromContact(data);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700877 }
878
879 @Override
Brian Attwell6bb01342014-08-20 23:16:00 -0700880 protected void onPostExecute(Cp2DataCardModel cardDataModel) {
881 super.onPostExecute(cardDataModel);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700882 // Check that original AsyncTask parameters are still valid and the activity
883 // is still running before binding to UI. A new intent could invalidate
884 // the results, for example.
885 if (data == mContactData && !isCancelled()) {
Brian Attwell6bb01342014-08-20 23:16:00 -0700886 bindDataToCards(cardDataModel);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700887 showActivity();
888 }
889 }
890 };
891 mEntriesAndActionsTask.execute();
892 }
893
Brian Attwell6bb01342014-08-20 23:16:00 -0700894 private void bindDataToCards(Cp2DataCardModel cp2DataCardModel) {
895 startInteractionLoaders(cp2DataCardModel);
896 populateContactAndAboutCard(cp2DataCardModel);
Paul Souloseb64a4b2014-07-07 17:03:27 -0700897 }
898
Brian Attwell6bb01342014-08-20 23:16:00 -0700899 private void startInteractionLoaders(Cp2DataCardModel cp2DataCardModel) {
900 final Map<String, List<DataItem>> dataItemsMap = cp2DataCardModel.dataItemsMap;
901 final List<DataItem> phoneDataItems = dataItemsMap.get(Phone.CONTENT_ITEM_TYPE);
Paul Soulos97f27802014-09-08 13:55:45 -0700902 if (phoneDataItems != null && phoneDataItems.size() == 1) {
903 mOnlyOnePhoneNumber = true;
904 }
Paul Souloseb64a4b2014-07-07 17:03:27 -0700905 String[] phoneNumbers = null;
906 if (phoneDataItems != null) {
907 phoneNumbers = new String[phoneDataItems.size()];
908 for (int i = 0; i < phoneDataItems.size(); ++i) {
909 phoneNumbers[i] = ((PhoneDataItem) phoneDataItems.get(i)).getNumber();
910 }
911 }
Paul Soulosab840442014-06-17 14:08:40 -0700912 final Bundle phonesExtraBundle = new Bundle();
Paul Souloseb64a4b2014-07-07 17:03:27 -0700913 phonesExtraBundle.putStringArray(KEY_LOADER_EXTRA_PHONES, phoneNumbers);
914
915 Trace.beginSection("start sms loader");
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700916 getLoaderManager().initLoader(
917 LOADER_SMS_ID,
Paul Soulosab840442014-06-17 14:08:40 -0700918 phonesExtraBundle,
919 mLoaderInteractionsCallbacks);
920 Trace.endSection();
921
922 Trace.beginSection("start call log loader");
923 getLoaderManager().initLoader(
924 LOADER_CALL_LOG_ID,
925 phonesExtraBundle,
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700926 mLoaderInteractionsCallbacks);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700927 Trace.endSection();
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700928
Paul Souloseb64a4b2014-07-07 17:03:27 -0700929
Paul Soulos899aa212014-06-11 12:04:43 -0700930 Trace.beginSection("start calendar loader");
Brian Attwell6bb01342014-08-20 23:16:00 -0700931 final List<DataItem> emailDataItems = dataItemsMap.get(Email.CONTENT_ITEM_TYPE);
Paul Soulos97f27802014-09-08 13:55:45 -0700932 if (emailDataItems != null && emailDataItems.size() == 1) {
933 mOnlyOneEmail = true;
934 }
Paul Souloseb64a4b2014-07-07 17:03:27 -0700935 String[] emailAddresses = null;
936 if (emailDataItems != null) {
937 emailAddresses = new String[emailDataItems.size()];
938 for (int i = 0; i < emailDataItems.size(); ++i) {
939 emailAddresses[i] = ((EmailDataItem) emailDataItems.get(i)).getAddress();
940 }
941 }
Paul Soulosab840442014-06-17 14:08:40 -0700942 final Bundle emailsExtraBundle = new Bundle();
Paul Souloseb64a4b2014-07-07 17:03:27 -0700943 emailsExtraBundle.putStringArray(KEY_LOADER_EXTRA_EMAILS, emailAddresses);
Paul Soulos899aa212014-06-11 12:04:43 -0700944 getLoaderManager().initLoader(
945 LOADER_CALENDAR_ID,
Paul Soulosab840442014-06-17 14:08:40 -0700946 emailsExtraBundle,
Paul Soulos899aa212014-06-11 12:04:43 -0700947 mLoaderInteractionsCallbacks);
948 Trace.endSection();
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700949 }
950
951 private void showActivity() {
952 if (mScroller != null) {
953 mScroller.setVisibility(View.VISIBLE);
954 SchedulingUtils.doOnPreDraw(mScroller, /* drawNextFrame = */ false,
955 new Runnable() {
956 @Override
957 public void run() {
958 runEntranceAnimation();
959 }
960 });
961 }
962 }
963
Brian Attwell6bb01342014-08-20 23:16:00 -0700964 private List<List<Entry>> buildAboutCardEntries(Map<String, List<DataItem>> dataItemsMap) {
Paul Soulosa153dba2014-08-06 13:44:57 -0700965 final List<List<Entry>> aboutCardEntries = new ArrayList<>();
966 for (String mimetype : SORTED_ABOUT_CARD_MIMETYPES) {
Brian Attwell6bb01342014-08-20 23:16:00 -0700967 final List<DataItem> mimeTypeItems = dataItemsMap.get(mimetype);
Paul Soulosa153dba2014-08-06 13:44:57 -0700968 if (mimeTypeItems == null) {
969 continue;
970 }
Brian Attwell6bb01342014-08-20 23:16:00 -0700971 // Set aboutCardTitleOut = null, since SORTED_ABOUT_CARD_MIMETYPES doesn't contain
972 // the name mimetype.
973 final List<Entry> aboutEntries = dataItemsToEntries(mimeTypeItems,
974 /* aboutCardTitleOut = */ null);
Paul Soulosa153dba2014-08-06 13:44:57 -0700975 if (aboutEntries.size() > 0) {
976 aboutCardEntries.add(aboutEntries);
977 }
978 }
979 return aboutCardEntries;
980 }
981
Paul Soulosa42ef762014-08-20 10:26:10 -0700982 @Override
983 protected void onResume() {
984 super.onResume();
985 // If returning from a launched activity, repopulate the contact and about card
986 if (mHasIntentLaunched) {
987 mHasIntentLaunched = false;
Paul Soulosa42ef762014-08-20 10:26:10 -0700988 }
989 }
990
Brian Attwell6bb01342014-08-20 23:16:00 -0700991 private void populateContactAndAboutCard(Cp2DataCardModel cp2DataCardModel) {
992 mCachedCp2DataCardModel = cp2DataCardModel;
993 if (mHasIntentLaunched || cp2DataCardModel == null) {
Paul Soulosa42ef762014-08-20 10:26:10 -0700994 return;
995 }
Paul Souloseb64a4b2014-07-07 17:03:27 -0700996 Trace.beginSection("bind contact card");
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700997
Brian Attwell6bb01342014-08-20 23:16:00 -0700998 final List<List<Entry>> contactCardEntries = cp2DataCardModel.contactCardEntries;
999 final List<List<Entry>> aboutCardEntries = cp2DataCardModel.aboutCardEntries;
1000 final String customAboutCardName = cp2DataCardModel.customAboutCardName;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001001
1002 if (contactCardEntries.size() > 0) {
1003 mContactCard.initialize(contactCardEntries,
1004 /* numInitialVisibleEntries = */ MIN_NUM_CONTACT_ENTRIES_SHOWN,
Paul Soulosc8e2a912014-07-21 14:52:20 -07001005 /* isExpanded = */ mContactCard.isExpanded(),
Paul Soulos89966b42014-07-21 12:38:50 -07001006 /* isAlwaysExpanded = */ false,
Paul Soulos0cda9ae2014-07-23 11:27:28 -07001007 mExpandingEntryCardViewListener,
1008 mScroller);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001009 mContactCard.setVisibility(View.VISIBLE);
1010 } else {
1011 mContactCard.setVisibility(View.GONE);
1012 }
1013 Trace.endSection();
1014
1015 Trace.beginSection("bind about card");
Paul Soulosc6e11092014-07-22 09:22:27 -07001016 // Phonetic name is not a data item, so the entry needs to be created separately
1017 final String phoneticName = mContactData.getPhoneticName();
1018 if (!TextUtils.isEmpty(phoneticName)) {
1019 Entry phoneticEntry = new Entry(/* viewId = */ -1,
1020 /* icon = */ null,
1021 getResources().getString(R.string.name_phonetic),
1022 phoneticName,
1023 /* text = */ null,
Paul Soulos23e28362014-08-29 14:57:08 -07001024 /* primaryContentDescription = */ null,
Paul Soulosc6e11092014-07-22 09:22:27 -07001025 /* intent = */ null,
1026 /* alternateIcon = */ null,
1027 /* alternateIntent = */ null,
1028 /* alternateContentDescription = */ null,
1029 /* shouldApplyColor = */ false,
Paul Soulos2a4207f2014-07-31 17:09:05 -07001030 /* isEditable = */ false,
1031 /* EntryContextMenuInfo = */ new EntryContextMenuInfo(phoneticName,
Paul Soulos97f27802014-09-08 13:55:45 -07001032 getResources().getString(R.string.name_phonetic),
1033 /* mimeType = */ null, /* id = */ -1, /* isPrimary = */ false),
Paul Soulos48fc9122014-08-26 13:52:36 -07001034 /* thirdIcon = */ null,
1035 /* thirdIntent = */ null,
Paul Soulos48290be2014-09-08 13:44:51 -07001036 /* thirdContentDescription = */ null,
1037 /* iconResourceId = */ 0);
Paul Soulosc6e11092014-07-22 09:22:27 -07001038 List<Entry> phoneticList = new ArrayList<>();
1039 phoneticList.add(phoneticEntry);
Paul Soulosa153dba2014-08-06 13:44:57 -07001040 // Phonetic name comes after nickname. Check to see if the first entry type is nickname
Paul Soulosa0fa4c12014-08-07 12:01:32 -07001041 if (aboutCardEntries.size() > 0 && aboutCardEntries.get(0).get(0).getHeader().equals(
Paul Soulosa153dba2014-08-06 13:44:57 -07001042 getResources().getString(R.string.header_nickname_entry))) {
1043 aboutCardEntries.add(1, phoneticList);
1044 } else {
1045 aboutCardEntries.add(0, phoneticList);
1046 }
Paul Soulosc6e11092014-07-22 09:22:27 -07001047 }
1048
Brian Attwell6bb01342014-08-20 23:16:00 -07001049 if (!TextUtils.isEmpty(customAboutCardName)) {
1050 mAboutCard.setTitle(customAboutCardName);
1051 }
1052
Paul Souloscc5ec222014-08-25 12:02:26 -07001053 if (aboutCardEntries.size() > 0) {
1054 mAboutCard.initialize(aboutCardEntries,
1055 /* numInitialVisibleEntries = */ 1,
1056 /* isExpanded = */ true,
1057 /* isAlwaysExpanded = */ true,
1058 mExpandingEntryCardViewListener,
1059 mScroller);
1060 }
Brian Attwell60953692014-07-11 17:18:46 -07001061
1062 if (contactCardEntries.size() == 0 && aboutCardEntries.size() == 0) {
1063 initializeNoContactDetailCard();
1064 } else {
1065 mNoContactDetailsCard.setVisibility(View.GONE);
1066 }
1067
Paul Soulosa8fce962014-07-15 13:37:24 -07001068 // If the Recent card is already initialized (all recent data is loaded), show the About
1069 // card if it has entries. Otherwise About card visibility will be set in bindRecentData()
1070 if (isAllRecentDataLoaded() && aboutCardEntries.size() > 0) {
1071 mAboutCard.setVisibility(View.VISIBLE);
1072 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001073 Trace.endSection();
1074 }
1075
1076 /**
Brian Attwell60953692014-07-11 17:18:46 -07001077 * Create a card that shows "Add email" and "Add phone number" entries in grey.
1078 */
1079 private void initializeNoContactDetailCard() {
1080 final Drawable phoneIcon = getResources().getDrawable(
1081 R.drawable.ic_phone_24dp).mutate();
1082 final Entry phonePromptEntry = new Entry(CARD_ENTRY_ID_EDIT_CONTACT,
1083 phoneIcon, getString(R.string.quickcontact_add_phone_number),
Paul Soulos23e28362014-08-29 14:57:08 -07001084 /* subHeader = */ null, /* text = */ null, /* primaryContentDescription = */ null,
1085 getEditContactIntent(),
Paul Soulosdd7419d2014-07-15 11:22:13 -07001086 /* alternateIcon = */ null, /* alternateIntent = */ null,
Paul Soulos714455b2014-07-15 14:32:01 -07001087 /* alternateContentDescription = */ null, /* shouldApplyColor = */ true,
Paul Soulos48fc9122014-08-26 13:52:36 -07001088 /* isEditable = */ false, /* EntryContextMenuInfo = */ null,
1089 /* thirdIcon = */ null, /* thirdIntent = */ null,
Paul Soulos48290be2014-09-08 13:44:51 -07001090 /* thirdContentDescription = */ null, R.drawable.ic_phone_24dp);
Brian Attwell60953692014-07-11 17:18:46 -07001091
1092 final Drawable emailIcon = getResources().getDrawable(
1093 R.drawable.ic_email_24dp).mutate();
1094 final Entry emailPromptEntry = new Entry(CARD_ENTRY_ID_EDIT_CONTACT,
1095 emailIcon, getString(R.string.quickcontact_add_email), /* subHeader = */ null,
Paul Soulos23e28362014-08-29 14:57:08 -07001096 /* text = */ null, /* primaryContentDescription = */ null,
1097 getEditContactIntent(), /* alternateIcon = */ null,
Paul Soulosdd7419d2014-07-15 11:22:13 -07001098 /* alternateIntent = */ null, /* alternateContentDescription = */ null,
Paul Soulos2a4207f2014-07-31 17:09:05 -07001099 /* shouldApplyColor = */ true, /* isEditable = */ false,
Paul Soulos48fc9122014-08-26 13:52:36 -07001100 /* EntryContextMenuInfo = */ null, /* thirdIcon = */ null,
Paul Soulos48290be2014-09-08 13:44:51 -07001101 /* thirdIntent = */ null, /* thirdContentDescription = */ null,
1102 R.drawable.ic_email_24dp);
Brian Attwell60953692014-07-11 17:18:46 -07001103
1104 final List<List<Entry>> promptEntries = new ArrayList<>();
1105 promptEntries.add(new ArrayList<Entry>(1));
1106 promptEntries.add(new ArrayList<Entry>(1));
1107 promptEntries.get(0).add(phonePromptEntry);
1108 promptEntries.get(1).add(emailPromptEntry);
1109
1110 final int subHeaderTextColor = getResources().getColor(
1111 R.color.quickcontact_entry_sub_header_text_color);
1112 final PorterDuffColorFilter greyColorFilter =
1113 new PorterDuffColorFilter(subHeaderTextColor, PorterDuff.Mode.SRC_ATOP);
Paul Soulosc8e2a912014-07-21 14:52:20 -07001114 mNoContactDetailsCard.initialize(promptEntries, 2, /* isExpanded = */ true,
Paul Soulos0cda9ae2014-07-23 11:27:28 -07001115 /* isAlwaysExpanded = */ true, mExpandingEntryCardViewListener, mScroller);
Brian Attwell60953692014-07-11 17:18:46 -07001116 mNoContactDetailsCard.setVisibility(View.VISIBLE);
1117 mNoContactDetailsCard.setEntryHeaderColor(subHeaderTextColor);
1118 mNoContactDetailsCard.setColorAndFilter(subHeaderTextColor, greyColorFilter);
1119 }
1120
1121 /**
Paul Souloseb64a4b2014-07-07 17:03:27 -07001122 * Builds the {@link DataItem}s Map out of the Contact.
1123 * @param data The contact to build the data from.
1124 * @return A pair containing a list of data items sorted within mimetype and sorted
1125 * amongst mimetype. The map goes from mimetype string to the sorted list of data items within
1126 * mimetype
1127 */
Brian Attwell6bb01342014-08-20 23:16:00 -07001128 private Cp2DataCardModel generateDataModelFromContact(
Paul Souloseb64a4b2014-07-07 17:03:27 -07001129 Contact data) {
1130 Trace.beginSection("Build data items map");
1131
1132 final Map<String, List<DataItem>> dataItemsMap = new HashMap<>();
Brian Attwell8bf96e72014-06-25 15:46:47 -07001133
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001134 final ResolveCache cache = ResolveCache.getInstance(this);
Maurice Chu851222a2012-06-21 11:43:08 -07001135 for (RawContact rawContact : data.getRawContacts()) {
1136 for (DataItem dataItem : rawContact.getDataItems()) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07001137 dataItem.setRawContactId(rawContact.getId());
1138
Maurice Chu851222a2012-06-21 11:43:08 -07001139 final String mimeType = dataItem.getMimeType();
Paul Souloseb64a4b2014-07-07 17:03:27 -07001140 if (mimeType == null) continue;
1141
Chiao Cheng47b6f702012-09-07 17:28:17 -07001142 final AccountType accountType = rawContact.getAccountType(this);
1143 final DataKind dataKind = AccountTypeManager.getInstance(this)
1144 .getKindOrFallback(accountType, mimeType);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001145 if (dataKind == null) continue;
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001146
Paul Souloseb64a4b2014-07-07 17:03:27 -07001147 dataItem.setDataKind(dataKind);
1148
1149 final boolean hasData = !TextUtils.isEmpty(dataItem.buildDataString(this,
1150 dataKind));
1151
1152 if (isMimeExcluded(mimeType) || !hasData) continue;
1153
1154 List<DataItem> dataItemListByType = dataItemsMap.get(mimeType);
1155 if (dataItemListByType == null) {
1156 dataItemListByType = new ArrayList<>();
1157 dataItemsMap.put(mimeType, dataItemListByType);
Paul Soulosb3054e52014-06-05 16:46:02 -07001158 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001159 dataItemListByType.add(dataItem);
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001160 }
1161 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001162 Trace.endSection();
Makoto Onukidfe8cc82012-05-17 18:03:44 -07001163
Paul Souloseb64a4b2014-07-07 17:03:27 -07001164 Trace.beginSection("sort within mimetypes");
Paul Soulos16339332014-06-24 13:59:56 -07001165 /*
1166 * Sorting is a multi part step. The end result is to a have a sorted list of the most
Paul Souloseb64a4b2014-07-07 17:03:27 -07001167 * used data items, one per mimetype. Then, within each mimetype, the list of data items
1168 * for that type is also sorted, based off of {super primary, primary, times used} in that
1169 * order.
Paul Soulos16339332014-06-24 13:59:56 -07001170 */
Paul Souloseb64a4b2014-07-07 17:03:27 -07001171 final List<List<DataItem>> dataItemsList = new ArrayList<>();
1172 for (List<DataItem> mimeTypeDataItems : dataItemsMap.values()) {
1173 // Remove duplicate data items
1174 Collapser.collapseList(mimeTypeDataItems, this);
1175 // Sort within mimetype
1176 Collections.sort(mimeTypeDataItems, mWithinMimeTypeDataItemComparator);
1177 // Add to the list of data item lists
1178 dataItemsList.add(mimeTypeDataItems);
1179 }
1180 Trace.endSection();
Paul Soulos16339332014-06-24 13:59:56 -07001181
Paul Souloseb64a4b2014-07-07 17:03:27 -07001182 Trace.beginSection("sort amongst mimetypes");
1183 // Sort amongst mimetypes to bubble up the top data items for the contact card
1184 Collections.sort(dataItemsList, mAmongstMimeTypeDataItemComparator);
1185 Trace.endSection();
1186
Brian Attwell6bb01342014-08-20 23:16:00 -07001187 Trace.beginSection("cp2 data items to entries");
1188
1189 final List<List<Entry>> contactCardEntries = new ArrayList<>();
1190 final List<List<Entry>> aboutCardEntries = buildAboutCardEntries(dataItemsMap);
1191 final MutableString aboutCardName = new MutableString();
1192
1193 for (int i = 0; i < dataItemsList.size(); ++i) {
1194 final List<DataItem> dataItemsByMimeType = dataItemsList.get(i);
1195 final DataItem topDataItem = dataItemsByMimeType.get(0);
1196 if (SORTED_ABOUT_CARD_MIMETYPES.contains(topDataItem.getMimeType())) {
1197 // About card mimetypes are built in buildAboutCardEntries, skip here
1198 continue;
1199 } else {
1200 List<Entry> contactEntries = dataItemsToEntries(dataItemsList.get(i),
1201 aboutCardName);
1202 if (contactEntries.size() > 0) {
1203 contactCardEntries.add(contactEntries);
1204 }
1205 }
1206 }
1207
1208 Trace.endSection();
1209
1210 final Cp2DataCardModel dataModel = new Cp2DataCardModel();
1211 dataModel.customAboutCardName = aboutCardName.value;
1212 dataModel.aboutCardEntries = aboutCardEntries;
1213 dataModel.contactCardEntries = contactCardEntries;
1214 dataModel.dataItemsMap = dataItemsMap;
1215 return dataModel;
1216 }
1217
1218 /**
1219 * Class used to hold the About card and Contact cards' data model that gets generated
1220 * on a background thread. All data is from CP2.
1221 */
1222 private static class Cp2DataCardModel {
1223 /**
1224 * A map between a mimetype string and the corresponding list of data items. The data items
1225 * are in sorted order using mWithinMimeTypeDataItemComparator.
1226 */
1227 public Map<String, List<DataItem>> dataItemsMap;
1228 public List<List<Entry>> aboutCardEntries;
1229 public List<List<Entry>> contactCardEntries;
1230 public String customAboutCardName;
1231 }
1232
1233 private static class MutableString {
1234 public String value;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001235 }
1236
1237 /**
1238 * Converts a {@link DataItem} into an {@link ExpandingEntryCardView.Entry} for display.
1239 * If the {@link ExpandingEntryCardView.Entry} has no visual elements, null is returned.
Brian Attwell6bb01342014-08-20 23:16:00 -07001240 *
1241 * This runs on a background thread. This is set as static to avoid accidentally adding
1242 * additional dependencies on unsafe things (like the Activity).
1243 *
Paul Souloseb64a4b2014-07-07 17:03:27 -07001244 * @param dataItem The {@link DataItem} to convert.
1245 * @return The {@link ExpandingEntryCardView.Entry}, or null if no visual elements are present.
1246 */
Brian Attwell6bb01342014-08-20 23:16:00 -07001247 private static Entry dataItemToEntry(DataItem dataItem,
1248 Context context, Contact contactData,
1249 final MutableString aboutCardName) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07001250 Drawable icon = null;
1251 String header = null;
1252 String subHeader = null;
1253 Drawable subHeaderIcon = null;
1254 String text = null;
1255 Drawable textIcon = null;
Paul Soulos23e28362014-08-29 14:57:08 -07001256 StringBuilder primaryContentDescription = new StringBuilder();
Paul Souloseb64a4b2014-07-07 17:03:27 -07001257 Intent intent = null;
Paul Soulos48ebbaa2014-07-15 13:11:23 -07001258 boolean shouldApplyColor = true;
Paul Soulosdd7419d2014-07-15 11:22:13 -07001259 Drawable alternateIcon = null;
1260 Intent alternateIntent = null;
Paul Soulos23e28362014-08-29 14:57:08 -07001261 StringBuilder alternateContentDescription = new StringBuilder();
Paul Souloseb64a4b2014-07-07 17:03:27 -07001262 final boolean isEditable = false;
Paul Soulos2a4207f2014-07-31 17:09:05 -07001263 EntryContextMenuInfo entryContextMenuInfo = null;
Paul Soulos48fc9122014-08-26 13:52:36 -07001264 Drawable thirdIcon = null;
1265 Intent thirdIntent = null;
1266 String thirdContentDescription = null;
Paul Soulos48290be2014-09-08 13:44:51 -07001267 int iconResourceId = 0;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001268
Brian Attwell6bb01342014-08-20 23:16:00 -07001269 context = context.getApplicationContext();
Paul Soulos23e28362014-08-29 14:57:08 -07001270 final Resources res = context.getResources();
Paul Souloseb64a4b2014-07-07 17:03:27 -07001271 DataKind kind = dataItem.getDataKind();
1272
1273 if (dataItem instanceof ImDataItem) {
1274 final ImDataItem im = (ImDataItem) dataItem;
Brian Attwell6bb01342014-08-20 23:16:00 -07001275 intent = ContactsUtils.buildImIntent(context, im).first;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001276 final boolean isEmail = im.isCreatedFromEmail();
Paul Soulos7de6f852014-07-28 14:07:28 -07001277 final int protocol;
1278 if (!im.isProtocolValid()) {
1279 protocol = Im.PROTOCOL_CUSTOM;
1280 } else {
1281 protocol = isEmail ? Im.PROTOCOL_GOOGLE_TALK : im.getProtocol();
1282 }
1283 if (protocol == Im.PROTOCOL_CUSTOM) {
1284 // If the protocol is custom, display the "IM" entry header as well to distinguish
1285 // this entry from other ones
Paul Soulos23e28362014-08-29 14:57:08 -07001286 header = res.getString(R.string.header_im_entry);
1287 subHeader = Im.getProtocolLabel(res, protocol,
Paul Soulos7de6f852014-07-28 14:07:28 -07001288 im.getCustomProtocol()).toString();
1289 text = im.getData();
1290 } else {
Paul Soulos23e28362014-08-29 14:57:08 -07001291 header = Im.getProtocolLabel(res, protocol,
Paul Soulos7de6f852014-07-28 14:07:28 -07001292 im.getCustomProtocol()).toString();
1293 subHeader = im.getData();
1294 }
Paul Soulos97f27802014-09-08 13:55:45 -07001295 entryContextMenuInfo = new EntryContextMenuInfo(im.getData(), header,
1296 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001297 } else if (dataItem instanceof OrganizationDataItem) {
1298 final OrganizationDataItem organization = (OrganizationDataItem) dataItem;
Paul Soulos23e28362014-08-29 14:57:08 -07001299 header = res.getString(R.string.header_organization_entry);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001300 subHeader = organization.getCompany();
Paul Soulos97f27802014-09-08 13:55:45 -07001301 entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header,
1302 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001303 text = organization.getTitle();
1304 } else if (dataItem instanceof NicknameDataItem) {
1305 final NicknameDataItem nickname = (NicknameDataItem) dataItem;
1306 // Build nickname entries
1307 final boolean isNameRawContact =
Brian Attwell6bb01342014-08-20 23:16:00 -07001308 (contactData.getNameRawContactId() == dataItem.getRawContactId());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001309
1310 final boolean duplicatesTitle =
1311 isNameRawContact
Brian Attwell6bb01342014-08-20 23:16:00 -07001312 && contactData.getDisplayNameSource() == DisplayNameSources.NICKNAME;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001313
1314 if (!duplicatesTitle) {
Paul Soulos23e28362014-08-29 14:57:08 -07001315 header = res.getString(R.string.header_nickname_entry);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001316 subHeader = nickname.getName();
Paul Soulos97f27802014-09-08 13:55:45 -07001317 entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header,
1318 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001319 }
1320 } else if (dataItem instanceof NoteDataItem) {
1321 final NoteDataItem note = (NoteDataItem) dataItem;
Paul Soulos23e28362014-08-29 14:57:08 -07001322 header = res.getString(R.string.header_note_entry);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001323 subHeader = note.getNote();
Paul Soulos97f27802014-09-08 13:55:45 -07001324 entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header,
1325 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001326 } else if (dataItem instanceof WebsiteDataItem) {
1327 final WebsiteDataItem website = (WebsiteDataItem) dataItem;
Paul Soulos23e28362014-08-29 14:57:08 -07001328 header = res.getString(R.string.header_website_entry);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001329 subHeader = website.getUrl();
Paul Soulos97f27802014-09-08 13:55:45 -07001330 entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header,
1331 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001332 try {
Brian Attwell6bb01342014-08-20 23:16:00 -07001333 final WebAddress webAddress = new WebAddress(website.buildDataString(context,
1334 kind));
Paul Souloseb64a4b2014-07-07 17:03:27 -07001335 intent = new Intent(Intent.ACTION_VIEW, Uri.parse(webAddress.toString()));
1336 } catch (final ParseException e) {
Brian Attwell6bb01342014-08-20 23:16:00 -07001337 Log.e(TAG, "Couldn't parse website: " + website.buildDataString(context, kind));
Paul Souloseb64a4b2014-07-07 17:03:27 -07001338 }
1339 } else if (dataItem instanceof EventDataItem) {
1340 final EventDataItem event = (EventDataItem) dataItem;
Brian Attwell6bb01342014-08-20 23:16:00 -07001341 final String dataString = event.buildDataString(context, kind);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001342 final Calendar cal = DateUtils.parseDate(dataString, false);
1343 if (cal != null) {
1344 final Date nextAnniversary =
1345 DateUtils.getNextAnnualDate(cal);
1346 final Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
1347 builder.appendPath("time");
1348 ContentUris.appendId(builder, nextAnniversary.getTime());
1349 intent = new Intent(Intent.ACTION_VIEW).setData(builder.build());
1350 }
Paul Soulos23e28362014-08-29 14:57:08 -07001351 header = res.getString(R.string.header_event_entry);
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001352 if (event.hasKindTypeColumn(kind)) {
Paul Soulos23e28362014-08-29 14:57:08 -07001353 subHeader = Event.getTypeLabel(res, event.getKindTypeColumn(kind),
Paul Soulos7de6f852014-07-28 14:07:28 -07001354 event.getLabel()).toString();
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001355 }
Brian Attwell6bb01342014-08-20 23:16:00 -07001356 text = DateUtils.formatDate(context, dataString);
Paul Soulos97f27802014-09-08 13:55:45 -07001357 entryContextMenuInfo = new EntryContextMenuInfo(text, header,
1358 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001359 } else if (dataItem instanceof RelationDataItem) {
1360 final RelationDataItem relation = (RelationDataItem) dataItem;
Brian Attwell6bb01342014-08-20 23:16:00 -07001361 final String dataString = relation.buildDataString(context, kind);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001362 if (!TextUtils.isEmpty(dataString)) {
1363 intent = new Intent(Intent.ACTION_SEARCH);
1364 intent.putExtra(SearchManager.QUERY, dataString);
1365 intent.setType(Contacts.CONTENT_TYPE);
1366 }
Paul Soulos23e28362014-08-29 14:57:08 -07001367 header = res.getString(R.string.header_relation_entry);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001368 subHeader = relation.getName();
Paul Soulos97f27802014-09-08 13:55:45 -07001369 entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header,
1370 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001371 if (relation.hasKindTypeColumn(kind)) {
Paul Soulos23e28362014-08-29 14:57:08 -07001372 text = Relation.getTypeLabel(res,
Brian Attwell6bb01342014-08-20 23:16:00 -07001373 relation.getKindTypeColumn(kind),
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001374 relation.getLabel()).toString();
1375 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001376 } else if (dataItem instanceof PhoneDataItem) {
1377 final PhoneDataItem phone = (PhoneDataItem) dataItem;
1378 if (!TextUtils.isEmpty(phone.getNumber())) {
Paul Soulos23e28362014-08-29 14:57:08 -07001379 primaryContentDescription.append(res.getString(R.string.call_other)).append(" ");
Brian Attwellc62cc792014-10-02 12:35:07 -07001380 header = sBidiFormatter.unicodeWrap(phone.buildDataString(context, kind),
1381 TextDirectionHeuristics.LTR);
Paul Soulos2a4207f2014-07-31 17:09:05 -07001382 entryContextMenuInfo = new EntryContextMenuInfo(header,
Paul Soulos97f27802014-09-08 13:55:45 -07001383 res.getString(R.string.phoneLabelsGroup), dataItem.getMimeType(),
1384 dataItem.getId(), dataItem.isSuperPrimary());
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001385 if (phone.hasKindTypeColumn(kind)) {
Paul Soulos23e28362014-08-29 14:57:08 -07001386 text = Phone.getTypeLabel(res, phone.getKindTypeColumn(kind),
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001387 phone.getLabel()).toString();
Paul Soulos23e28362014-08-29 14:57:08 -07001388 primaryContentDescription.append(text).append(" ");
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001389 }
Paul Soulos23e28362014-08-29 14:57:08 -07001390 primaryContentDescription.append(header);
1391 icon = res.getDrawable(R.drawable.ic_phone_24dp);
Paul Soulos48290be2014-09-08 13:44:51 -07001392 iconResourceId = R.drawable.ic_phone_24dp;
Brian Attwell6bb01342014-08-20 23:16:00 -07001393 if (PhoneCapabilityTester.isPhone(context)) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07001394 intent = CallUtil.getCallIntent(phone.getNumber());
Paul Soulos16339332014-06-24 13:59:56 -07001395 }
Paul Soulosdd7419d2014-07-15 11:22:13 -07001396 alternateIntent = new Intent(Intent.ACTION_SENDTO,
Jay Shrauner1cd88e32014-09-05 15:37:55 -07001397 Uri.fromParts(ContactsUtils.SCHEME_SMSTO, phone.getNumber(), null));
Paul Soulos48fc9122014-08-26 13:52:36 -07001398
Paul Soulos23e28362014-08-29 14:57:08 -07001399 alternateIcon = res.getDrawable(R.drawable.ic_message_24dp);
1400 alternateContentDescription.append(res.getString(R.string.sms_custom, header));
Paul Soulos48fc9122014-08-26 13:52:36 -07001401
1402 // Add video call button if supported
Paul Soulosf7647302014-08-26 14:28:57 -07001403 if (CallUtil.isVideoEnabled(context)) {
Paul Soulos23e28362014-08-29 14:57:08 -07001404 thirdIcon = res.getDrawable(R.drawable.ic_videocam);
Paul Soulos48fc9122014-08-26 13:52:36 -07001405 thirdIntent = CallUtil.getVideoCallIntent(phone.getNumber(),
1406 CALL_ORIGIN_QUICK_CONTACTS_ACTIVITY);
1407 thirdContentDescription =
Paul Soulos23e28362014-08-29 14:57:08 -07001408 res.getString(R.string.description_video_call);
Paul Soulos48fc9122014-08-26 13:52:36 -07001409 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001410 }
1411 } else if (dataItem instanceof EmailDataItem) {
1412 final EmailDataItem email = (EmailDataItem) dataItem;
1413 final String address = email.getData();
1414 if (!TextUtils.isEmpty(address)) {
Paul Soulos23e28362014-08-29 14:57:08 -07001415 primaryContentDescription.append(res.getString(R.string.email_other)).append(" ");
Jay Shrauner1cd88e32014-09-05 15:37:55 -07001416 final Uri mailUri = Uri.fromParts(ContactsUtils.SCHEME_MAILTO, address, null);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001417 intent = new Intent(Intent.ACTION_SENDTO, mailUri);
1418 header = email.getAddress();
Paul Soulos2a4207f2014-07-31 17:09:05 -07001419 entryContextMenuInfo = new EntryContextMenuInfo(header,
Paul Soulos97f27802014-09-08 13:55:45 -07001420 res.getString(R.string.emailLabelsGroup), dataItem.getMimeType(),
1421 dataItem.getId(), dataItem.isSuperPrimary());
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001422 if (email.hasKindTypeColumn(kind)) {
Paul Soulos23e28362014-08-29 14:57:08 -07001423 text = Email.getTypeLabel(res, email.getKindTypeColumn(kind),
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001424 email.getLabel()).toString();
Paul Soulos23e28362014-08-29 14:57:08 -07001425 primaryContentDescription.append(text).append(" ");
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001426 }
Paul Soulos23e28362014-08-29 14:57:08 -07001427 primaryContentDescription.append(header);
1428 icon = res.getDrawable(R.drawable.ic_email_24dp);
Paul Soulos48290be2014-09-08 13:44:51 -07001429 iconResourceId = R.drawable.ic_email_24dp;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001430 }
1431 } else if (dataItem instanceof StructuredPostalDataItem) {
1432 StructuredPostalDataItem postal = (StructuredPostalDataItem) dataItem;
1433 final String postalAddress = postal.getFormattedAddress();
1434 if (!TextUtils.isEmpty(postalAddress)) {
Paul Soulos23e28362014-08-29 14:57:08 -07001435 primaryContentDescription.append(res.getString(R.string.map_other)).append(" ");
Paul Souloseb64a4b2014-07-07 17:03:27 -07001436 intent = StructuredPostalUtils.getViewPostalAddressIntent(postalAddress);
1437 header = postal.getFormattedAddress();
Paul Soulos2a4207f2014-07-31 17:09:05 -07001438 entryContextMenuInfo = new EntryContextMenuInfo(header,
Paul Soulos97f27802014-09-08 13:55:45 -07001439 res.getString(R.string.postalLabelsGroup), dataItem.getMimeType(),
1440 dataItem.getId(), dataItem.isSuperPrimary());
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001441 if (postal.hasKindTypeColumn(kind)) {
Paul Soulos23e28362014-08-29 14:57:08 -07001442 text = StructuredPostal.getTypeLabel(res,
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001443 postal.getKindTypeColumn(kind), postal.getLabel()).toString();
Paul Soulos23e28362014-08-29 14:57:08 -07001444 primaryContentDescription.append(text).append(" ");
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001445 }
Paul Soulos23e28362014-08-29 14:57:08 -07001446 primaryContentDescription.append(header);
Paul Soulos6a4d2732014-07-22 14:22:12 -07001447 alternateIntent =
1448 StructuredPostalUtils.getViewPostalAddressDirectionsIntent(postalAddress);
Paul Soulos23e28362014-08-29 14:57:08 -07001449 alternateIcon = res.getDrawable(R.drawable.ic_directions_24dp);
1450 alternateContentDescription.append(res.getString(
1451 R.string.content_description_directions)).append(" ").append(header);
1452 icon = res.getDrawable(R.drawable.ic_place_24dp);
Paul Soulos48290be2014-09-08 13:44:51 -07001453 iconResourceId = R.drawable.ic_place_24dp;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001454 }
1455 } else if (dataItem instanceof SipAddressDataItem) {
Brian Attwell6bb01342014-08-20 23:16:00 -07001456 if (PhoneCapabilityTester.isSipPhone(context)) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07001457 final SipAddressDataItem sip = (SipAddressDataItem) dataItem;
1458 final String address = sip.getSipAddress();
1459 if (!TextUtils.isEmpty(address)) {
Paul Soulos23e28362014-08-29 14:57:08 -07001460 primaryContentDescription.append(res.getString(R.string.call_other)).append(
1461 " ");
Jay Shrauner1cd88e32014-09-05 15:37:55 -07001462 final Uri callUri = Uri.fromParts(PhoneAccount.SCHEME_SIP, address, null);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001463 intent = CallUtil.getCallIntent(callUri);
Paul Soulos1cda01b2014-07-21 12:22:00 -07001464 header = address;
Paul Soulos2a4207f2014-07-31 17:09:05 -07001465 entryContextMenuInfo = new EntryContextMenuInfo(header,
Paul Soulos97f27802014-09-08 13:55:45 -07001466 res.getString(R.string.phoneLabelsGroup), dataItem.getMimeType(),
1467 dataItem.getId(), dataItem.isSuperPrimary());
Paul Soulos1cda01b2014-07-21 12:22:00 -07001468 if (sip.hasKindTypeColumn(kind)) {
Paul Soulos23e28362014-08-29 14:57:08 -07001469 text = SipAddress.getTypeLabel(res,
Brian Attwell6bb01342014-08-20 23:16:00 -07001470 sip.getKindTypeColumn(kind), sip.getLabel()).toString();
Paul Soulos23e28362014-08-29 14:57:08 -07001471 primaryContentDescription.append(text).append(" ");
Paul Soulos1cda01b2014-07-21 12:22:00 -07001472 }
Paul Soulos23e28362014-08-29 14:57:08 -07001473 primaryContentDescription.append(header);
1474 icon = res.getDrawable(R.drawable.ic_dialer_sip_black_24dp);
Paul Soulos48290be2014-09-08 13:44:51 -07001475 iconResourceId = R.drawable.ic_dialer_sip_black_24dp;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001476 }
1477 }
1478 } else if (dataItem instanceof StructuredNameDataItem) {
1479 final String givenName = ((StructuredNameDataItem) dataItem).getGivenName();
1480 if (!TextUtils.isEmpty(givenName)) {
Paul Soulos23e28362014-08-29 14:57:08 -07001481 aboutCardName.value = res.getString(R.string.about_card_title) +
Brian Attwell6bb01342014-08-20 23:16:00 -07001482 " " + givenName;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001483 } else {
Paul Soulos23e28362014-08-29 14:57:08 -07001484 aboutCardName.value = res.getString(R.string.about_card_title);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001485 }
1486 } else {
1487 // Custom DataItem
Brian Attwell6bb01342014-08-20 23:16:00 -07001488 header = dataItem.buildDataStringForDisplay(context, kind);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001489 text = kind.typeColumn;
1490 intent = new Intent(Intent.ACTION_VIEW);
Paul Soulos60e51082014-07-10 12:33:04 -07001491 final Uri uri = ContentUris.withAppendedId(Data.CONTENT_URI, dataItem.getId());
1492 intent.setDataAndType(uri, dataItem.getMimeType());
Paul Soulose0055662014-07-10 16:33:08 -07001493
1494 if (intent != null) {
1495 final String mimetype = intent.getType();
1496
1497 // Attempt to use known icons for known 3p types. Otherwise default to ResolveCache
1498 switch (mimetype) {
1499 case MIMETYPE_GPLUS_PROFILE:
1500 if (INTENT_DATA_GPLUS_PROFILE_ADD_TO_CIRCLE.equals(
1501 intent.getDataString())) {
Paul Soulos23e28362014-08-29 14:57:08 -07001502 icon = res.getDrawable(
Paul Soulose0055662014-07-10 16:33:08 -07001503 R.drawable.ic_add_to_circles_black_24);
Paul Soulos48290be2014-09-08 13:44:51 -07001504 iconResourceId = R.drawable.ic_add_to_circles_black_24;
Paul Soulose0055662014-07-10 16:33:08 -07001505 } else {
Paul Soulos23e28362014-08-29 14:57:08 -07001506 icon = res.getDrawable(R.drawable.ic_google_plus_24dp);
Paul Soulos48290be2014-09-08 13:44:51 -07001507 iconResourceId = R.drawable.ic_google_plus_24dp;
Paul Soulose0055662014-07-10 16:33:08 -07001508 }
1509 break;
1510 case MIMETYPE_HANGOUTS:
1511 if (INTENT_DATA_HANGOUTS_VIDEO.equals(intent.getDataString())) {
Paul Soulos23e28362014-08-29 14:57:08 -07001512 icon = res.getDrawable(R.drawable.ic_hangout_video_24dp);
Paul Soulos48290be2014-09-08 13:44:51 -07001513 iconResourceId = R.drawable.ic_hangout_video_24dp;
Paul Soulose0055662014-07-10 16:33:08 -07001514 } else {
Paul Soulos23e28362014-08-29 14:57:08 -07001515 icon = res.getDrawable(R.drawable.ic_hangout_24dp);
Paul Soulos48290be2014-09-08 13:44:51 -07001516 iconResourceId = R.drawable.ic_hangout_24dp;
Paul Soulose0055662014-07-10 16:33:08 -07001517 }
1518 break;
1519 default:
Paul Soulos97f27802014-09-08 13:55:45 -07001520 entryContextMenuInfo = new EntryContextMenuInfo(header, mimetype,
1521 dataItem.getMimeType(), dataItem.getId(),
1522 dataItem.isSuperPrimary());
Brian Attwell6bb01342014-08-20 23:16:00 -07001523 icon = ResolveCache.getInstance(context).getIcon(
Paul Soulose0055662014-07-10 16:33:08 -07001524 dataItem.getMimeType(), intent);
1525 // Call mutate to create a new Drawable.ConstantState for color filtering
1526 if (icon != null) {
1527 icon.mutate();
1528 }
Paul Soulos48ebbaa2014-07-15 13:11:23 -07001529 shouldApplyColor = false;
Paul Soulose0055662014-07-10 16:33:08 -07001530 }
1531 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001532 }
1533
Paul Souloseb64a4b2014-07-07 17:03:27 -07001534 if (intent != null) {
1535 // Do not set the intent is there are no resolves
Brian Attwell6bb01342014-08-20 23:16:00 -07001536 if (!PhoneCapabilityTester.isIntentRegistered(context, intent)) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07001537 intent = null;
Paul Soulos16339332014-06-24 13:59:56 -07001538 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001539 }
Paul Soulos16339332014-06-24 13:59:56 -07001540
Paul Soulosdd7419d2014-07-15 11:22:13 -07001541 if (alternateIntent != null) {
1542 // Do not set the alternate intent is there are no resolves
Brian Attwell6bb01342014-08-20 23:16:00 -07001543 if (!PhoneCapabilityTester.isIntentRegistered(context, alternateIntent)) {
Paul Soulosdd7419d2014-07-15 11:22:13 -07001544 alternateIntent = null;
Jay Shraunerf0d04c12014-09-02 13:44:48 -07001545 } else if (TextUtils.isEmpty(alternateContentDescription)) {
1546 // Attempt to use package manager to find a suitable content description if needed
Paul Soulos23e28362014-08-29 14:57:08 -07001547 alternateContentDescription.append(getIntentResolveLabel(alternateIntent, context));
Paul Soulosdd7419d2014-07-15 11:22:13 -07001548 }
1549 }
1550
Paul Souloseb64a4b2014-07-07 17:03:27 -07001551 // If the Entry has no visual elements, return null
1552 if (icon == null && TextUtils.isEmpty(header) && TextUtils.isEmpty(subHeader) &&
1553 subHeaderIcon == null && TextUtils.isEmpty(text) && textIcon == null) {
1554 return null;
1555 }
1556
Brian Attwellfa238942014-08-12 10:21:28 -07001557 // Ignore dataIds from the Me profile.
Paul Soulosea5e0b72014-07-08 18:09:44 -07001558 final int dataId = dataItem.getId() > Integer.MAX_VALUE ?
1559 -1 : (int) dataItem.getId();
1560
Paul Soulos23e28362014-08-29 14:57:08 -07001561 return new Entry(dataId, icon, header, subHeader, subHeaderIcon, text, textIcon,
1562 primaryContentDescription.toString(), intent, alternateIcon, alternateIntent,
1563 alternateContentDescription.toString(), shouldApplyColor, isEditable,
Paul Soulos48290be2014-09-08 13:44:51 -07001564 entryContextMenuInfo, thirdIcon, thirdIntent, thirdContentDescription,
1565 iconResourceId);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001566 }
1567
Brian Attwell6bb01342014-08-20 23:16:00 -07001568 private List<Entry> dataItemsToEntries(List<DataItem> dataItems,
1569 MutableString aboutCardTitleOut) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07001570 final List<Entry> entries = new ArrayList<>();
1571 for (DataItem dataItem : dataItems) {
Brian Attwell6bb01342014-08-20 23:16:00 -07001572 final Entry entry = dataItemToEntry(dataItem, this, mContactData, aboutCardTitleOut);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001573 if (entry != null) {
1574 entries.add(entry);
1575 }
1576 }
1577 return entries;
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001578 }
1579
Brian Attwell6bb01342014-08-20 23:16:00 -07001580 private static String getIntentResolveLabel(Intent intent, Context context) {
1581 final List<ResolveInfo> matches = context.getPackageManager().queryIntentActivities(intent,
Paul Soulosdd7419d2014-07-15 11:22:13 -07001582 PackageManager.MATCH_DEFAULT_ONLY);
1583
1584 // Pick first match, otherwise best found
1585 ResolveInfo bestResolve = null;
1586 final int size = matches.size();
1587 if (size == 1) {
1588 bestResolve = matches.get(0);
1589 } else if (size > 1) {
Brian Attwell6bb01342014-08-20 23:16:00 -07001590 bestResolve = ResolveCache.getInstance(context).getBestResolve(intent, matches);
Paul Soulosdd7419d2014-07-15 11:22:13 -07001591 }
1592
1593 if (bestResolve == null) {
1594 return null;
1595 }
1596
Brian Attwell6bb01342014-08-20 23:16:00 -07001597 return String.valueOf(bestResolve.loadLabel(context.getPackageManager()));
Paul Soulosdd7419d2014-07-15 11:22:13 -07001598 }
1599
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001600 /**
Brian Attwell31b2d422014-06-05 00:14:58 -07001601 * Asynchronously extract the most vibrant color from the PhotoView. Once extracted,
1602 * apply this tint to {@link MultiShrinkScroller}. This operation takes about 20-30ms
1603 * on a Nexus 5.
1604 */
1605 private void extractAndApplyTintFromPhotoViewAsynchronously() {
1606 if (mScroller == null) {
1607 return;
1608 }
1609 final Drawable imageViewDrawable = mPhotoView.getDrawable();
Brian Attwellfaf97392014-07-10 18:32:30 -07001610 new AsyncTask<Void, Void, MaterialPalette>() {
Brian Attwell31b2d422014-06-05 00:14:58 -07001611 @Override
Brian Attwellfaf97392014-07-10 18:32:30 -07001612 protected MaterialPalette doInBackground(Void... params) {
1613
Brian Attwell95c268e2014-08-26 22:04:15 -07001614 if (imageViewDrawable instanceof BitmapDrawable
1615 && mContactData.getThumbnailPhotoBinaryData() != null
1616 && mContactData.getThumbnailPhotoBinaryData().length > 0) {
1617 // Perform the color analysis on the thumbnail instead of the full sized
1618 // image, so that our results will be as similar as possible to the Bugle
1619 // app.
1620 final Bitmap bitmap = BitmapFactory.decodeByteArray(
1621 mContactData.getThumbnailPhotoBinaryData(), 0,
1622 mContactData.getThumbnailPhotoBinaryData().length);
1623 try {
1624 final int primaryColor = colorFromBitmap(bitmap);
1625 if (primaryColor != 0) {
1626 return mMaterialColorMapUtils.calculatePrimaryAndSecondaryColor(
1627 primaryColor);
1628 }
1629 } finally {
1630 bitmap.recycle();
Brian Attwellfaf97392014-07-10 18:32:30 -07001631 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001632 }
1633 if (imageViewDrawable instanceof LetterTileDrawable) {
Brian Attwellfaf97392014-07-10 18:32:30 -07001634 final int primaryColor = ((LetterTileDrawable) imageViewDrawable).getColor();
Brian Attwella3859ed2014-07-15 13:51:55 -07001635 return mMaterialColorMapUtils.calculatePrimaryAndSecondaryColor(primaryColor);
Brian Attwell31b2d422014-06-05 00:14:58 -07001636 }
Brian Attwella3859ed2014-07-15 13:51:55 -07001637 return MaterialColorMapUtils.getDefaultPrimaryAndSecondaryColors(getResources());
Brian Attwell31b2d422014-06-05 00:14:58 -07001638 }
1639
1640 @Override
Brian Attwellfaf97392014-07-10 18:32:30 -07001641 protected void onPostExecute(MaterialPalette palette) {
1642 super.onPostExecute(palette);
Brian Attwell8571dd32014-06-23 23:29:10 -07001643 if (mHasComputedThemeColor) {
1644 // If we had previously computed a theme color from the contact photo,
1645 // then do not update the theme color. Changing the theme color several
1646 // seconds after QC has started, as a result of an updated/upgraded photo,
1647 // is a jarring experience. On the other hand, changing the theme color after
1648 // a rotation or onNewIntent() is perfectly fine.
1649 return;
1650 }
1651 // Check that the Photo has not changed. If it has changed, the new tint
1652 // color needs to be extracted
1653 if (imageViewDrawable == mPhotoView.getDrawable()) {
1654 mHasComputedThemeColor = true;
Brian Attwellfaf97392014-07-10 18:32:30 -07001655 setThemeColor(palette);
Brian Attwell31b2d422014-06-05 00:14:58 -07001656 }
1657 }
1658 }.execute();
1659 }
1660
Brian Attwell4936a922014-07-02 16:16:59 -07001661 /**
1662 * Examine how many white pixels are in the bitmap in order to determine whether or not
1663 * we need gradient overlays on top of the image.
1664 */
1665 private void analyzeWhitenessOfPhotoAsynchronously() {
1666 final Drawable imageViewDrawable = mPhotoView.getDrawable();
1667 new AsyncTask<Void, Void, Boolean>() {
1668 @Override
1669 protected Boolean doInBackground(Void... params) {
1670 if (imageViewDrawable instanceof BitmapDrawable) {
1671 final Bitmap bitmap = ((BitmapDrawable) imageViewDrawable).getBitmap();
1672 return WhitenessUtils.isBitmapWhiteAtTopOrBottom(bitmap);
1673 }
1674 return !(imageViewDrawable instanceof LetterTileDrawable);
1675 }
1676
1677 @Override
1678 protected void onPostExecute(Boolean isWhite) {
1679 super.onPostExecute(isWhite);
1680 mScroller.setUseGradient(isWhite);
1681 }
1682 }.execute();
1683 }
1684
Brian Attwellfaf97392014-07-10 18:32:30 -07001685 private void setThemeColor(MaterialPalette palette) {
Brian Attwell9b889e62014-06-23 18:25:32 -07001686 // If the color is invalid, use the predefined default
Brian Attwellfaf97392014-07-10 18:32:30 -07001687 final int primaryColor = palette.mPrimaryColor;
1688 mScroller.setHeaderTintColor(primaryColor);
1689 mStatusBarColor = palette.mSecondaryColor;
Brian Attwell9b889e62014-06-23 18:25:32 -07001690 updateStatusBarColor();
Brian Attwell8571dd32014-06-23 23:29:10 -07001691
Brian Attwell9b889e62014-06-23 18:25:32 -07001692 mColorFilter =
Brian Attwellfaf97392014-07-10 18:32:30 -07001693 new PorterDuffColorFilter(primaryColor, PorterDuff.Mode.SRC_ATOP);
1694 mContactCard.setColorAndFilter(primaryColor, mColorFilter);
1695 mRecentCard.setColorAndFilter(primaryColor, mColorFilter);
1696 mAboutCard.setColorAndFilter(primaryColor, mColorFilter);
Brian Attwell9b889e62014-06-23 18:25:32 -07001697 }
1698
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001699 private void updateStatusBarColor() {
1700 if (mScroller == null) {
1701 return;
1702 }
1703 final int desiredStatusBarColor;
1704 // Only use a custom status bar color if QuickContacts touches the top of the viewport.
1705 if (mScroller.getScrollNeededToBeFullScreen() <= 0) {
1706 desiredStatusBarColor = mStatusBarColor;
1707 } else {
1708 desiredStatusBarColor = Color.TRANSPARENT;
1709 }
1710 // Animate to the new color.
Brian Attwell847bf2c2014-07-24 13:59:27 -07001711 final ObjectAnimator animation = ObjectAnimator.ofInt(getWindow(), "statusBarColor",
1712 getWindow().getStatusBarColor(), desiredStatusBarColor);
1713 animation.setDuration(ANIMATION_STATUS_BAR_COLOR_CHANGE_DURATION);
1714 animation.setEvaluator(new ArgbEvaluator());
1715 animation.start();
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001716 }
1717
1718 private int colorFromBitmap(Bitmap bitmap) {
1719 // Author of Palette recommends using 24 colors when analyzing profile photos.
1720 final int NUMBER_OF_PALETTE_COLORS = 24;
1721 final Palette palette = Palette.generate(bitmap, NUMBER_OF_PALETTE_COLORS);
Brian Attwella0f20f72014-07-07 11:51:52 -07001722 if (palette != null && palette.getVibrantSwatch() != null) {
1723 return palette.getVibrantSwatch().getRgb();
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001724 }
1725 return 0;
1726 }
1727
Paul Soulosb3054e52014-06-05 16:46:02 -07001728 private List<Entry> contactInteractionsToEntries(List<ContactInteraction> interactions) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07001729 final List<Entry> entries = new ArrayList<>();
Paul Soulosb3054e52014-06-05 16:46:02 -07001730 for (ContactInteraction interaction : interactions) {
Paul Soulosea5e0b72014-07-08 18:09:44 -07001731 entries.add(new Entry(/* id = */ -1,
1732 interaction.getIcon(this),
Paul Soulosb3054e52014-06-05 16:46:02 -07001733 interaction.getViewHeader(this),
1734 interaction.getViewBody(this),
1735 interaction.getBodyIcon(this),
1736 interaction.getViewFooter(this),
1737 interaction.getFooterIcon(this),
Paul Soulos23e28362014-08-29 14:57:08 -07001738 interaction.getContentDescription(this),
Paul Soulosb3054e52014-06-05 16:46:02 -07001739 interaction.getIntent(),
Paul Soulosdd7419d2014-07-15 11:22:13 -07001740 /* alternateIcon = */ null,
1741 /* alternateIntent = */ null,
1742 /* alternateContentDescription = */ null,
Paul Soulos48ebbaa2014-07-15 13:11:23 -07001743 /* shouldApplyColor = */ true,
Paul Soulos2a4207f2014-07-31 17:09:05 -07001744 /* isEditable = */ false,
Paul Soulos48fc9122014-08-26 13:52:36 -07001745 /* EntryContextMenuInfo = */ null,
1746 /* thirdIcon = */ null,
1747 /* thirdIntent = */ null,
Paul Soulos48290be2014-09-08 13:44:51 -07001748 /* thirdContentDescription = */ null,
1749 interaction.getIconResourceId()));
Paul Soulosb3054e52014-06-05 16:46:02 -07001750 }
1751 return entries;
1752 }
1753
Paul Souloseb64a4b2014-07-07 17:03:27 -07001754 private final LoaderCallbacks<Contact> mLoaderContactCallbacks =
Maurice Chu851222a2012-06-21 11:43:08 -07001755 new LoaderCallbacks<Contact>() {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02001756 @Override
Maurice Chu851222a2012-06-21 11:43:08 -07001757 public void onLoaderReset(Loader<Contact> loader) {
Paul Soulos405ae402014-07-14 16:16:36 -07001758 mContactData = null;
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02001759 }
1760
1761 @Override
Maurice Chu851222a2012-06-21 11:43:08 -07001762 public void onLoadFinished(Loader<Contact> loader, Contact data) {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001763 Trace.beginSection("onLoadFinished()");
Jay Shraunerded85a72014-12-12 11:29:16 -08001764 try {
1765
1766 if (isFinishing()) {
1767 return;
1768 }
1769 if (data.isError()) {
1770 // This means either the contact is invalid or we had an
1771 // internal error such as an acore crash.
1772 Log.i(TAG, "Failed to load contact: " + ((ContactLoader)loader).getLookupUri());
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001773 Toast.makeText(QuickContactActivity.this, R.string.invalidContactMessage,
1774 Toast.LENGTH_LONG).show();
gaoyl199702501712015-05-15 17:10:24 +08001775 finish();
Jay Shraunerded85a72014-12-12 11:29:16 -08001776 return;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001777 }
Jay Shraunerded85a72014-12-12 11:29:16 -08001778 if (data.isNotFound()) {
1779 if (mHasAlreadyBeenOpened) {
1780 finish();
1781 } else {
1782 Log.i(TAG, "No contact found: " + ((ContactLoader)loader).getLookupUri());
1783 Toast.makeText(QuickContactActivity.this, R.string.invalidContactMessage,
1784 Toast.LENGTH_LONG).show();
1785 finish();
1786 }
1787 return;
1788 }
1789
1790 bindContactData(data);
1791
1792 } finally {
1793 Trace.endSection();
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02001794 }
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02001795 }
1796
1797 @Override
Maurice Chu851222a2012-06-21 11:43:08 -07001798 public Loader<Contact> onCreateLoader(int id, Bundle args) {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02001799 if (mLookupUri == null) {
1800 Log.wtf(TAG, "Lookup uri wasn't initialized. Loader was started too early");
1801 }
Brian Attwelld28851f2014-06-10 13:25:07 -07001802 // Load all contact data. We need loadGroupMetaData=true to determine whether the
1803 // contact is invisible. If it is, we need to display an "Add to Contacts" MenuItem.
Yorke Leeb2b435a2012-11-12 16:47:06 -08001804 return new ContactLoader(getApplicationContext(), mLookupUri,
Brian Attwelld28851f2014-06-10 13:25:07 -07001805 true /*loadGroupMetaData*/, false /*loadInvitableAccountTypes*/,
Brian Attwell8571dd32014-06-23 23:29:10 -07001806 true /*postViewNotification*/, true /*computeFormattedPhoneNumber*/);
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02001807 }
1808 };
Paul Soulosb3054e52014-06-05 16:46:02 -07001809
Brian Attwellb7e43642014-06-02 14:33:04 -07001810 @Override
1811 public void onBackPressed() {
1812 if (mScroller != null) {
Brian Attwell8477eaf2014-06-18 15:39:50 -07001813 if (!mIsExitAnimationInProgress) {
1814 mScroller.scrollOffBottom();
1815 }
Brian Attwellb7e43642014-06-02 14:33:04 -07001816 } else {
1817 super.onBackPressed();
1818 }
1819 }
Paul Soulosb3054e52014-06-05 16:46:02 -07001820
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001821 @Override
1822 public void finish() {
1823 super.finish();
1824
1825 // override transitions to skip the standard window animations
1826 overridePendingTransition(0, 0);
1827 }
1828
Paul Souloseb64a4b2014-07-07 17:03:27 -07001829 private final LoaderCallbacks<List<ContactInteraction>> mLoaderInteractionsCallbacks =
Paul Soulosb3054e52014-06-05 16:46:02 -07001830 new LoaderCallbacks<List<ContactInteraction>>() {
1831
1832 @Override
1833 public Loader<List<ContactInteraction>> onCreateLoader(int id, Bundle args) {
1834 Log.v(TAG, "onCreateLoader");
1835 Loader<List<ContactInteraction>> loader = null;
1836 switch (id) {
1837 case LOADER_SMS_ID:
1838 Log.v(TAG, "LOADER_SMS_ID");
1839 loader = new SmsInteractionsLoader(
1840 QuickContactActivity.this,
Paul Soulosab840442014-06-17 14:08:40 -07001841 args.getStringArray(KEY_LOADER_EXTRA_PHONES),
Paul Soulosb3054e52014-06-05 16:46:02 -07001842 MAX_SMS_RETRIEVE);
1843 break;
Paul Soulos899aa212014-06-11 12:04:43 -07001844 case LOADER_CALENDAR_ID:
1845 Log.v(TAG, "LOADER_CALENDAR_ID");
Paul Soulosae4cafe2014-07-09 14:11:03 -07001846 final String[] emailsArray = args.getStringArray(KEY_LOADER_EXTRA_EMAILS);
1847 List<String> emailsList = null;
1848 if (emailsArray != null) {
1849 emailsList = Arrays.asList(args.getStringArray(KEY_LOADER_EXTRA_EMAILS));
1850 }
Paul Soulos899aa212014-06-11 12:04:43 -07001851 loader = new CalendarInteractionsLoader(
1852 QuickContactActivity.this,
Paul Soulosae4cafe2014-07-09 14:11:03 -07001853 emailsList,
Paul Soulos899aa212014-06-11 12:04:43 -07001854 MAX_FUTURE_CALENDAR_RETRIEVE,
1855 MAX_PAST_CALENDAR_RETRIEVE,
1856 FUTURE_MILLISECOND_TO_SEARCH_LOCAL_CALENDAR,
1857 PAST_MILLISECOND_TO_SEARCH_LOCAL_CALENDAR);
1858 break;
Paul Soulosab840442014-06-17 14:08:40 -07001859 case LOADER_CALL_LOG_ID:
1860 Log.v(TAG, "LOADER_CALL_LOG_ID");
1861 loader = new CallLogInteractionsLoader(
1862 QuickContactActivity.this,
1863 args.getStringArray(KEY_LOADER_EXTRA_PHONES),
1864 MAX_CALL_LOG_RETRIEVE);
Paul Soulosb3054e52014-06-05 16:46:02 -07001865 }
1866 return loader;
1867 }
1868
1869 @Override
1870 public void onLoadFinished(Loader<List<ContactInteraction>> loader,
1871 List<ContactInteraction> data) {
Paul Soulosb3054e52014-06-05 16:46:02 -07001872 mRecentLoaderResults.put(loader.getId(), data);
1873
1874 if (isAllRecentDataLoaded()) {
1875 bindRecentData();
1876 }
1877 }
1878
1879 @Override
1880 public void onLoaderReset(Loader<List<ContactInteraction>> loader) {
1881 mRecentLoaderResults.remove(loader.getId());
1882 }
Paul Soulosb3054e52014-06-05 16:46:02 -07001883 };
1884
1885 private boolean isAllRecentDataLoaded() {
1886 return mRecentLoaderResults.size() == mRecentLoaderIds.length;
1887 }
1888
1889 private void bindRecentData() {
Paul Souloseb64a4b2014-07-07 17:03:27 -07001890 final List<ContactInteraction> allInteractions = new ArrayList<>();
Brian Attwell30e1ef12014-09-02 15:49:20 -07001891 final List<List<Entry>> interactionsWrapper = new ArrayList<>();
Paul Soulosb3054e52014-06-05 16:46:02 -07001892
Brian Attwell30e1ef12014-09-02 15:49:20 -07001893 mRecentDataTask = new AsyncTask<Void, Void, Void>() {
Paul Soulosb3054e52014-06-05 16:46:02 -07001894 @Override
Brian Attwell30e1ef12014-09-02 15:49:20 -07001895 protected Void doInBackground(Void... params) {
1896 Trace.beginSection("sort recent loader results");
Paul Soulosb3054e52014-06-05 16:46:02 -07001897
Brian Attwell30e1ef12014-09-02 15:49:20 -07001898 for (List<ContactInteraction> loaderInteractions : mRecentLoaderResults.values()) {
1899 allInteractions.addAll(loaderInteractions);
1900 }
1901
1902 // Sort the interactions by most recent
1903 Collections.sort(allInteractions, new Comparator<ContactInteraction>() {
1904 @Override
1905 public int compare(ContactInteraction a, ContactInteraction b) {
1906 return a.getInteractionDate() >= b.getInteractionDate() ? -1 : 1;
1907 }
1908 });
1909
1910 Trace.endSection();
1911 Trace.beginSection("contactInteractionsToEntries");
1912
1913 // Wrap each interaction in its own list so that an icon is displayed for each entry
1914 for (Entry contactInteraction : contactInteractionsToEntries(allInteractions)) {
1915 List<Entry> entryListWrapper = new ArrayList<>(1);
1916 entryListWrapper.add(contactInteraction);
1917 interactionsWrapper.add(entryListWrapper);
1918 }
1919
1920 Trace.endSection();
1921 return null;
1922 }
1923
1924 @Override
1925 protected void onPostExecute(Void aVoid) {
1926 super.onPostExecute(aVoid);
1927 Trace.beginSection("initialize recents card");
1928
1929 if (allInteractions.size() > 0) {
1930 mRecentCard.initialize(interactionsWrapper,
Paul Soulosb3054e52014-06-05 16:46:02 -07001931 /* numInitialVisibleEntries = */ MIN_NUM_COLLAPSED_RECENT_ENTRIES_SHOWN,
Paul Soulosc8e2a912014-07-21 14:52:20 -07001932 /* isExpanded = */ mRecentCard.isExpanded(), /* isAlwaysExpanded = */ false,
Brian Attwell30e1ef12014-09-02 15:49:20 -07001933 mExpandingEntryCardViewListener, mScroller);
1934 mRecentCard.setVisibility(View.VISIBLE);
1935 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001936
Brian Attwell30e1ef12014-09-02 15:49:20 -07001937 Trace.endSection();
1938
1939 // About card is initialized along with the contact card, but since it appears after
1940 // the recent card in the UI, we hold off until making it visible until the recent
1941 // card is also ready to avoid stuttering.
1942 if (mAboutCard.shouldShow()) {
1943 mAboutCard.setVisibility(View.VISIBLE);
1944 } else {
1945 mAboutCard.setVisibility(View.GONE);
1946 }
1947 mRecentDataTask = null;
1948 }
1949 };
1950 mRecentDataTask.execute();
Paul Soulosb3054e52014-06-05 16:46:02 -07001951 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001952
1953 @Override
1954 protected void onStop() {
1955 super.onStop();
1956
1957 if (mEntriesAndActionsTask != null) {
1958 // Once the activity is stopped, we will no longer want to bind mEntriesAndActionsTask's
1959 // results on the UI thread. In some circumstances Activities are killed without
1960 // onStop() being called. This is not a problem, because in these circumstances
1961 // the entire process will be killed.
1962 mEntriesAndActionsTask.cancel(/* mayInterruptIfRunning = */ false);
1963 }
Brian Attwell30e1ef12014-09-02 15:49:20 -07001964 if (mRecentDataTask != null) {
1965 mRecentDataTask.cancel(/* mayInterruptIfRunning = */ false);
1966 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001967 }
Paul Soulos23889ba2014-06-10 14:55:32 -07001968
1969 /**
Brian Attwelld28851f2014-06-10 13:25:07 -07001970 * Returns true if it is possible to edit the current contact.
1971 */
1972 private boolean isContactEditable() {
1973 return mContactData != null && !mContactData.isDirectoryEntry();
1974 }
1975
Brian Attwella41d6d12014-07-31 15:23:13 -07001976 /**
1977 * Returns true if it is possible to share the current contact.
1978 */
1979 private boolean isContactShareable() {
1980 return mContactData != null && !mContactData.isDirectoryEntry();
1981 }
1982
Brian Attwell60953692014-07-11 17:18:46 -07001983 private Intent getEditContactIntent() {
Brian Attwell63176c92014-08-18 15:14:18 -07001984 final Intent intent = new Intent(Intent.ACTION_EDIT, mContactData.getLookupUri());
Brian Attwelld28851f2014-06-10 13:25:07 -07001985 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
Brian Attwell60953692014-07-11 17:18:46 -07001986 return intent;
1987 }
1988
1989 private void editContact() {
Paul Soulosa42ef762014-08-20 10:26:10 -07001990 mHasIntentLaunched = true;
Zheng Fuc00a0b52014-09-02 16:44:44 -07001991 mContactLoader.cacheResult();
Brian Attwell60953692014-07-11 17:18:46 -07001992 startActivityForResult(getEditContactIntent(), REQUEST_CODE_CONTACT_EDITOR_ACTIVITY);
Brian Attwelld28851f2014-06-10 13:25:07 -07001993 }
1994
1995 private void toggleStar(MenuItem starredMenuItem) {
1996 // Make sure there is a contact
Brian Attwell63176c92014-08-18 15:14:18 -07001997 if (mContactData != null) {
Brian Attwelld28851f2014-06-10 13:25:07 -07001998 // Read the current starred value from the UI instead of using the last
1999 // loaded state. This allows rapid tapping without writing the same
2000 // value several times
2001 final boolean isStarred = starredMenuItem.isChecked();
2002
2003 // To improve responsiveness, swap out the picture (and tag) in the UI already
Paul Soulos333091a2014-07-22 13:54:41 -07002004 ContactDisplayUtils.configureStarredMenuItem(starredMenuItem,
Brian Attwelld28851f2014-06-10 13:25:07 -07002005 mContactData.isDirectoryEntry(), mContactData.isUserProfile(),
2006 !isStarred);
2007
2008 // Now perform the real save
Paul Souloseb64a4b2014-07-07 17:03:27 -07002009 final Intent intent = ContactSaveService.createSetStarredIntent(
Brian Attwell63176c92014-08-18 15:14:18 -07002010 QuickContactActivity.this, mContactData.getLookupUri(), !isStarred);
Brian Attwelld28851f2014-06-10 13:25:07 -07002011 startService(intent);
Brian Attwell35ccdba2014-07-18 11:43:23 -07002012
2013 final CharSequence accessibilityText = !isStarred
2014 ? getResources().getText(R.string.description_action_menu_add_star)
2015 : getResources().getText(R.string.description_action_menu_remove_star);
2016 // Accessibility actions need to have an associated view. We can't access the MenuItem's
2017 // underlying view, so put this accessibility action on the root view.
2018 mScroller.announceForAccessibility(accessibilityText);
Brian Attwelld28851f2014-06-10 13:25:07 -07002019 }
2020 }
2021
Brian Attwell752cccf2014-06-10 16:24:04 -07002022 /**
2023 * Calls into the contacts provider to get a pre-authorized version of the given URI.
2024 */
2025 private Uri getPreAuthorizedUri(Uri uri) {
2026 final Bundle uriBundle = new Bundle();
2027 uriBundle.putParcelable(ContactsContract.Authorization.KEY_URI_TO_AUTHORIZE, uri);
2028 final Bundle authResponse = getContentResolver().call(
2029 ContactsContract.AUTHORITY_URI,
2030 ContactsContract.Authorization.AUTHORIZATION_METHOD,
2031 null,
2032 uriBundle);
2033 if (authResponse != null) {
2034 return (Uri) authResponse.getParcelable(
2035 ContactsContract.Authorization.KEY_AUTHORIZED_URI);
2036 } else {
2037 return uri;
2038 }
2039 }
Brian Attwell2d150da2014-07-09 22:35:56 -07002040
zhuqk0617ab8d6ff2015-05-06 15:08:32 +08002041 private void doDeleteContact(Uri contactUri) {
2042 final Intent intent = ContactSaveService.createDeleteContactIntent(
2043 QuickContactActivity.this, contactUri);
2044 startService(intent);
2045 }
2046
2047 private void deleteContact() {
2048 if(mContactData != null) {
2049 final Uri delUri = mContactData.getLookupUri();
2050 AlertDialog mDialog = new AlertDialog.Builder(this)
2051 .setIconAttribute(android.R.attr.alertDialogIcon)
2052 .setMessage(R.string.deleteConfirmation)
2053 .setNegativeButton(android.R.string.cancel, null)
2054 .setPositiveButton(android.R.string.ok,
2055 new DialogInterface.OnClickListener() {
2056 @Override
2057 public void onClick(DialogInterface dialog, int whichButton) {
2058 doDeleteContact(delUri);
2059 }
2060 }
2061 )
2062 .create();
2063 mDialog.show();
2064 }
2065 }
2066
Brian Attwell752cccf2014-06-10 16:24:04 -07002067 private void shareContact() {
2068 final String lookupKey = mContactData.getLookupKey();
2069 Uri shareUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey);
2070 if (mContactData.isUserProfile()) {
2071 // User is sharing the profile. We don't want to force the receiver to have
2072 // the highly-privileged READ_PROFILE permission, so we need to request a
2073 // pre-authorized URI from the provider.
2074 shareUri = getPreAuthorizedUri(shareUri);
2075 }
2076
2077 final Intent intent = new Intent(Intent.ACTION_SEND);
2078 intent.setType(Contacts.CONTENT_VCARD_TYPE);
2079 intent.putExtra(Intent.EXTRA_STREAM, shareUri);
2080
2081 // Launch chooser to share contact via
2082 final CharSequence chooseTitle = getText(R.string.share_via);
2083 final Intent chooseIntent = Intent.createChooser(intent, chooseTitle);
2084
2085 try {
Paul Soulosa42ef762014-08-20 10:26:10 -07002086 mHasIntentLaunched = true;
Brian Attwell752cccf2014-06-10 16:24:04 -07002087 this.startActivity(chooseIntent);
Paul Souloseb64a4b2014-07-07 17:03:27 -07002088 } catch (final ActivityNotFoundException ex) {
Brian Attwell752cccf2014-06-10 16:24:04 -07002089 Toast.makeText(this, R.string.share_error, Toast.LENGTH_SHORT).show();
2090 }
2091 }
2092
2093 /**
2094 * Creates a launcher shortcut with the current contact.
2095 */
2096 private void createLauncherShortcutWithContact() {
2097 final ShortcutIntentBuilder builder = new ShortcutIntentBuilder(this,
2098 new OnShortcutIntentCreatedListener() {
2099
2100 @Override
2101 public void onShortcutIntentCreated(Uri uri, Intent shortcutIntent) {
2102 // Broadcast the shortcutIntent to the launcher to create a
2103 // shortcut to this contact
2104 shortcutIntent.setAction(ACTION_INSTALL_SHORTCUT);
2105 QuickContactActivity.this.sendBroadcast(shortcutIntent);
2106
2107 // Send a toast to give feedback to the user that a shortcut to this
2108 // contact was added to the launcher.
2109 Toast.makeText(QuickContactActivity.this,
2110 R.string.createContactShortcutSuccessful,
2111 Toast.LENGTH_SHORT).show();
2112 }
2113
2114 });
Brian Attwell63176c92014-08-18 15:14:18 -07002115 builder.createContactShortcutIntent(mContactData.getLookupUri());
Brian Attwell752cccf2014-06-10 16:24:04 -07002116 }
2117
Brian Attwell66965e12014-09-08 16:15:20 -07002118 private boolean isShortcutCreatable() {
2119 final Intent createShortcutIntent = new Intent();
2120 createShortcutIntent.setAction(ACTION_INSTALL_SHORTCUT);
2121 final List<ResolveInfo> receivers = getPackageManager()
2122 .queryBroadcastReceivers(createShortcutIntent, 0);
2123 return receivers != null && receivers.size() > 0;
2124 }
2125
Brian Attwelld28851f2014-06-10 13:25:07 -07002126 @Override
2127 public boolean onCreateOptionsMenu(Menu menu) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07002128 final MenuInflater inflater = getMenuInflater();
Brian Attwelld28851f2014-06-10 13:25:07 -07002129 inflater.inflate(R.menu.quickcontact, menu);
2130 return true;
2131 }
2132
2133 @Override
2134 public boolean onPrepareOptionsMenu(Menu menu) {
2135 if (mContactData != null) {
2136 final MenuItem starredMenuItem = menu.findItem(R.id.menu_star);
Paul Soulos333091a2014-07-22 13:54:41 -07002137 ContactDisplayUtils.configureStarredMenuItem(starredMenuItem,
Brian Attwelld28851f2014-06-10 13:25:07 -07002138 mContactData.isDirectoryEntry(), mContactData.isUserProfile(),
2139 mContactData.getStarred());
Brian Attwella41d6d12014-07-31 15:23:13 -07002140
Brian Attwelld28851f2014-06-10 13:25:07 -07002141 // Configure edit MenuItem
2142 final MenuItem editMenuItem = menu.findItem(R.id.menu_edit);
2143 editMenuItem.setVisible(true);
2144 if (DirectoryContactUtil.isDirectoryContact(mContactData) || InvisibleContactUtil
2145 .isInvisibleAndAddable(mContactData, this)) {
Brian Attwell30cfd122014-06-13 16:42:11 -07002146 editMenuItem.setIcon(R.drawable.ic_person_add_tinted_24dp);
Brian Attwell2e4214c2014-07-10 22:03:16 -07002147 editMenuItem.setTitle(R.string.menu_add_contact);
Brian Attwelld28851f2014-06-10 13:25:07 -07002148 } else if (isContactEditable()) {
2149 editMenuItem.setIcon(R.drawable.ic_create_24dp);
Brian Attwell2e4214c2014-07-10 22:03:16 -07002150 editMenuItem.setTitle(R.string.menu_editContact);
Brian Attwelld28851f2014-06-10 13:25:07 -07002151 } else {
2152 editMenuItem.setVisible(false);
2153 }
Brian Attwella41d6d12014-07-31 15:23:13 -07002154
2155 final MenuItem shareMenuItem = menu.findItem(R.id.menu_share);
2156 shareMenuItem.setVisible(isContactShareable());
2157
Brian Attwell66965e12014-09-08 16:15:20 -07002158 final MenuItem shortcutMenuItem = menu.findItem(R.id.menu_create_contact_shortcut);
2159 shortcutMenuItem.setVisible(isShortcutCreatable());
2160
Paul Soulos8a6d0022014-06-16 15:11:03 -07002161 return true;
Brian Attwelld28851f2014-06-10 13:25:07 -07002162 }
Paul Soulos8a6d0022014-06-16 15:11:03 -07002163 return false;
Brian Attwelld28851f2014-06-10 13:25:07 -07002164 }
2165
2166 @Override
2167 public boolean onOptionsItemSelected(MenuItem item) {
2168 switch (item.getItemId()) {
2169 case R.id.menu_star:
2170 toggleStar(item);
2171 return true;
2172 case R.id.menu_edit:
2173 if (DirectoryContactUtil.isDirectoryContact(mContactData)) {
Paul Soulosab54ea12014-08-28 17:22:38 -07002174 // This action is used to launch the contact selector, with the option of
2175 // creating a new contact. Creating a new contact is an INSERT, while selecting
2176 // an exisiting one is an edit. The fields in the edit screen will be
2177 // prepopulated with data.
2178
2179 final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
2180 intent.setType(Contacts.CONTENT_ITEM_TYPE);
2181
2182 // Only pre-fill the name field if the provided display name is an organization
2183 // name or better (e.g. structured name, nickname)
2184 if (mContactData.getDisplayNameSource() >= DisplayNameSources.ORGANIZATION) {
2185 intent.putExtra(Intents.Insert.NAME, mContactData.getDisplayName());
2186 }
2187 ArrayList<ContentValues> values = mContactData.getContentValues();
2188 // Last time used and times used are aggregated values from the usage stat
2189 // table. They need to be removed from data values so the SQL table can insert
2190 // properly
2191 for (ContentValues value : values) {
2192 value.remove(Data.LAST_TIME_USED);
2193 value.remove(Data.TIMES_USED);
2194 }
2195 intent.putExtra(Intents.Insert.DATA, values);
2196
2197 // If the contact can only export to the same account, add it to the intent.
2198 // Otherwise the ContactEditorFragment will show a dialog for selecting an
2199 // account.
2200 if (mContactData.getDirectoryExportSupport() ==
2201 Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY) {
2202 intent.putExtra(Intents.Insert.ACCOUNT,
2203 new Account(mContactData.getDirectoryAccountName(),
2204 mContactData.getDirectoryAccountType()));
2205 intent.putExtra(Intents.Insert.DATA_SET,
2206 mContactData.getRawContacts().get(0).getDataSet());
2207 }
2208
Paul Soulosf19dda92014-09-15 15:48:02 -07002209 // Add this flag to disable the delete menu option on directory contact joins
2210 // with local contacts. The delete option is ambiguous when joining contacts.
2211 intent.putExtra(ContactEditorFragment.INTENT_EXTRA_DISABLE_DELETE_MENU_OPTION,
2212 true);
2213
Paul Soulosab54ea12014-08-28 17:22:38 -07002214 startActivityForResult(intent, REQUEST_CODE_CONTACT_SELECTION_ACTIVITY);
Brian Attwelld28851f2014-06-10 13:25:07 -07002215 } else if (InvisibleContactUtil.isInvisibleAndAddable(mContactData, this)) {
2216 InvisibleContactUtil.addToDefaultGroup(mContactData, this);
2217 } else if (isContactEditable()) {
2218 editContact();
2219 }
2220 return true;
zhuqk0617ab8d6ff2015-05-06 15:08:32 +08002221 case R.id.menu_delete:
2222 deleteContact();
2223 return true;
Brian Attwell752cccf2014-06-10 16:24:04 -07002224 case R.id.menu_share:
2225 shareContact();
2226 return true;
2227 case R.id.menu_create_contact_shortcut:
2228 createLauncherShortcutWithContact();
2229 return true;
Brian Attwelld28851f2014-06-10 13:25:07 -07002230 default:
2231 return super.onOptionsItemSelected(item);
2232 }
Paul Soulos23889ba2014-06-10 14:55:32 -07002233 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -07002234}