blob: fcd0a50bf51e71c411cf5cf2c94cc5de06b49122 [file] [log] [blame]
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001/*
yaolu79525d02016-08-24 12:08:39 -07002
Daniel Lehmannedb576a2011-07-27 16:45:13 -07003 * Copyright (C) 2009 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package com.android.contacts.quickcontact;
19
Walter Jang0e723d92016-07-07 19:00:30 -070020import android.Manifest;
Paul Soulosab54ea12014-08-28 17:22:38 -070021import android.accounts.Account;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -070022import android.animation.ArgbEvaluator;
Brian Attwellb7e43642014-06-02 14:33:04 -070023import android.animation.ObjectAnimator;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070024import android.app.Activity;
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +020025import android.app.LoaderManager.LoaderCallbacks;
Gary Maib9065dd2016-11-08 10:49:00 -080026import android.app.ProgressDialog;
Paul Souloseb64a4b2014-07-07 17:03:27 -070027import android.app.SearchManager;
Brian Attwell752cccf2014-06-10 16:24:04 -070028import android.content.ActivityNotFoundException;
Gary Maib9065dd2016-11-08 10:49:00 -080029import android.content.BroadcastReceiver;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070030import android.content.ContentUris;
Paul Soulosea5e0b72014-07-08 18:09:44 -070031import android.content.ContentValues;
Brian Attwell6bb01342014-08-20 23:16:00 -070032import android.content.Context;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070033import android.content.Intent;
Gary Maib9065dd2016-11-08 10:49:00 -080034import android.content.IntentFilter;
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +020035import android.content.Loader;
Paul Soulosdd7419d2014-07-15 11:22:13 -070036import android.content.pm.PackageManager;
37import android.content.pm.ResolveInfo;
Paul Soulos23e28362014-08-29 14:57:08 -070038import android.content.res.Resources;
Brian Attwell31b2d422014-06-05 00:14:58 -070039import android.graphics.Bitmap;
Brian Attwell95c268e2014-08-26 22:04:15 -070040import android.graphics.BitmapFactory;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -070041import android.graphics.Color;
Paul Souloseb64a4b2014-07-07 17:03:27 -070042import android.graphics.PorterDuff;
43import android.graphics.PorterDuffColorFilter;
Brian Attwell31b2d422014-06-05 00:14:58 -070044import android.graphics.drawable.BitmapDrawable;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -070045import android.graphics.drawable.ColorDrawable;
Brian Attwell31b2d422014-06-05 00:14:58 -070046import android.graphics.drawable.Drawable;
yaolu58a1ac22016-10-24 16:50:24 -070047import android.media.RingtoneManager;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070048import android.net.Uri;
Brian Attwell31b2d422014-06-05 00:14:58 -070049import android.os.AsyncTask;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070050import android.os.Bundle;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -070051import android.os.Trace;
Paul Souloseb64a4b2014-07-07 17:03:27 -070052import android.provider.CalendarContract;
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;
Walter Jang8bac28b2016-08-30 10:34:55 -070068import android.provider.ContactsContract.DataUsageFeedback;
Paul Soulosab54ea12014-08-28 17:22:38 -070069import android.provider.ContactsContract.Directory;
Paul Souloseb64a4b2014-07-07 17:03:27 -070070import android.provider.ContactsContract.DisplayNameSources;
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;
guanxiongliu8e56edf2016-07-14 13:48:55 -070074import android.support.graphics.drawable.VectorDrawableCompat;
guanxiongliu04a19362016-05-18 14:38:51 -070075import android.support.v4.app.ActivityCompat;
Gary Maib9065dd2016-11-08 10:49:00 -080076import android.support.v4.content.LocalBroadcastManager;
Brian Attwell31b2d422014-06-05 00:14:58 -070077import android.support.v7.graphics.Palette;
Tyler Gunn03192222014-09-10 15:20:09 -070078import android.telecom.PhoneAccount;
79import android.telecom.TelecomManager;
Brian Attwellc62cc792014-10-02 12:35:07 -070080import android.text.BidiFormatter;
Tingting Wangb6949dc2015-11-03 13:17:12 -080081import android.text.Spannable;
Walter Jang7ce53522014-10-29 13:26:43 -070082import android.text.SpannableString;
Brian Attwellc62cc792014-10-02 12:35:07 -070083import android.text.TextDirectionHeuristics;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070084import android.text.TextUtils;
85import android.util.Log;
Paul Soulos2a4207f2014-07-31 17:09:05 -070086import android.view.ContextMenu;
87import android.view.ContextMenu.ContextMenuInfo;
Brian Attwelld28851f2014-06-10 13:25:07 -070088import android.view.Menu;
Brian Attwelld28851f2014-06-10 13:25:07 -070089import android.view.MenuInflater;
Paul Souloseb64a4b2014-07-07 17:03:27 -070090import android.view.MenuItem;
Nancy Chenf619e432014-08-18 20:15:12 -070091import android.view.MotionEvent;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070092import android.view.View;
93import android.view.View.OnClickListener;
Paul Soulos2a4207f2014-07-31 17:09:05 -070094import android.view.View.OnCreateContextMenuListener;
Daisuke Miyakawa10d7df72011-07-29 16:11:05 -070095import android.view.WindowManager;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070096import android.widget.Toast;
Brian Attwelld28851f2014-06-10 13:25:07 -070097import android.widget.Toolbar;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070098
Christine Chen72b3ab12013-08-13 23:22:34 +020099import com.android.contacts.ContactSaveService;
Brian Attwelld28851f2014-06-10 13:25:07 -0700100import com.android.contacts.ContactsActivity;
Marcus Hagerottd105c1e2016-09-30 14:28:00 -0700101import com.android.contacts.DynamicShortcuts;
Brian Attwell2d150da2014-07-09 22:35:56 -0700102import com.android.contacts.NfcHandler;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -0700103import com.android.contacts.R;
Gary Mai363af602016-09-28 10:01:23 -0700104import com.android.contacts.activities.ContactEditorActivity;
yaolu2de7c8e2016-09-26 09:45:44 -0700105import com.android.contacts.activities.ContactSelectionActivity;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700106import com.android.contacts.common.CallUtil;
Paul Soulos2a4207f2014-07-31 17:09:05 -0700107import com.android.contacts.common.ClipboardUtils;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700108import com.android.contacts.common.Collapser;
109import com.android.contacts.common.ContactsUtils;
Walter Jangf6236372016-02-22 21:24:22 +0000110import com.android.contacts.common.activity.RequestDesiredPermissionsActivity;
Brian Attwellbdd32642015-05-08 17:03:15 -0700111import com.android.contacts.common.activity.RequestPermissionsActivity;
Wenyi Wang4c3d3e22015-12-17 14:30:02 -0800112import com.android.contacts.common.compat.CompatUtils;
Wenyi Wangc85af282015-12-21 15:55:32 -0800113import com.android.contacts.common.compat.EventCompat;
Wenyi Wangcaf26192016-05-09 15:00:25 -0700114import com.android.contacts.common.compat.MultiWindowCompat;
Tyler Gunn5f87e922015-08-05 14:24:52 -0700115import com.android.contacts.common.dialog.CallSubjectDialog;
Nancy Chen70e3f4f2014-08-13 16:04:43 -0700116import com.android.contacts.common.interactions.TouchPointManager;
Brian Attwell31b2d422014-06-05 00:14:58 -0700117import com.android.contacts.common.lettertiles.LetterTileDrawable;
Brian Attwell752cccf2014-06-10 16:24:04 -0700118import com.android.contacts.common.list.ShortcutIntentBuilder;
119import com.android.contacts.common.list.ShortcutIntentBuilder.OnShortcutIntentCreatedListener;
Walter Jang62a20742016-02-24 17:04:12 -0800120import com.android.contacts.common.logging.Logger;
yaolu79525d02016-08-24 12:08:39 -0700121import com.android.contacts.common.logging.QuickContactEvent.ActionType;
Marcus Hagerottd105c1e2016-09-30 14:28:00 -0700122import com.android.contacts.common.logging.QuickContactEvent.CardType;
123import com.android.contacts.common.logging.QuickContactEvent.ContactType;
124import com.android.contacts.common.logging.ScreenEvent.ScreenType;
Chiao Cheng0d5588d2012-11-26 15:34:14 -0800125import com.android.contacts.common.model.AccountTypeManager;
Yorke Leecd321f62013-10-28 15:20:15 -0700126import com.android.contacts.common.model.Contact;
127import com.android.contacts.common.model.ContactLoader;
128import com.android.contacts.common.model.RawContact;
Chiao Cheng428f0082012-11-13 18:38:56 -0800129import com.android.contacts.common.model.account.AccountType;
Walter Jang8bac28b2016-08-30 10:34:55 -0700130import com.android.contacts.common.model.dataitem.CustomDataItem;
Yorke Leecd321f62013-10-28 15:20:15 -0700131import com.android.contacts.common.model.dataitem.DataItem;
Chiao Cheng428f0082012-11-13 18:38:56 -0800132import com.android.contacts.common.model.dataitem.DataKind;
Yorke Leecd321f62013-10-28 15:20:15 -0700133import com.android.contacts.common.model.dataitem.EmailDataItem;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700134import com.android.contacts.common.model.dataitem.EventDataItem;
Yorke Leecd321f62013-10-28 15:20:15 -0700135import com.android.contacts.common.model.dataitem.ImDataItem;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700136import com.android.contacts.common.model.dataitem.NicknameDataItem;
137import com.android.contacts.common.model.dataitem.NoteDataItem;
138import com.android.contacts.common.model.dataitem.OrganizationDataItem;
Paul Soulosb3054e52014-06-05 16:46:02 -0700139import com.android.contacts.common.model.dataitem.PhoneDataItem;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700140import com.android.contacts.common.model.dataitem.RelationDataItem;
141import com.android.contacts.common.model.dataitem.SipAddressDataItem;
142import com.android.contacts.common.model.dataitem.StructuredNameDataItem;
143import com.android.contacts.common.model.dataitem.StructuredPostalDataItem;
144import com.android.contacts.common.model.dataitem.WebsiteDataItem;
145import com.android.contacts.common.util.DateUtils;
Walter Jang8bac28b2016-08-30 10:34:55 -0700146import com.android.contacts.common.util.ImplicitIntentsUtil;
Brian Attwellfaf97392014-07-10 18:32:30 -0700147import com.android.contacts.common.util.MaterialColorMapUtils;
148import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
guanxiongliu04a19362016-05-18 14:38:51 -0700149import com.android.contacts.common.util.PermissionsUtil;
Tyler Gunn5f87e922015-08-05 14:24:52 -0700150import com.android.contacts.common.util.UriUtils;
Brian Attwelld41ab8a2014-08-07 11:08:55 -0700151import com.android.contacts.common.util.ViewUtil;
Paul Soulos333091a2014-07-22 13:54:41 -0700152import com.android.contacts.detail.ContactDisplayUtils;
Gary Mai363af602016-09-28 10:01:23 -0700153import com.android.contacts.editor.ContactEditorFragment;
Walter Jang5a7a23b2015-03-06 10:54:26 -0800154import com.android.contacts.editor.EditorIntents;
yaolu58a1ac22016-10-24 16:50:24 -0700155import com.android.contacts.editor.EditorUiUtils;
Paul Soulos899aa212014-06-11 12:04:43 -0700156import com.android.contacts.interactions.CalendarInteractionsLoader;
Paul Soulosab840442014-06-17 14:08:40 -0700157import com.android.contacts.interactions.CallLogInteractionsLoader;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700158import com.android.contacts.interactions.ContactDeletionInteraction;
Paul Soulosb3054e52014-06-05 16:46:02 -0700159import com.android.contacts.interactions.ContactInteraction;
Paul Soulosb3054e52014-06-05 16:46:02 -0700160import com.android.contacts.interactions.SmsInteractionsLoader;
yaolu2de7c8e2016-09-26 09:45:44 -0700161import com.android.contacts.list.UiIntentActions;
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700162import com.android.contacts.quickcontact.ExpandingEntryCardView.Entry;
Paul Soulos2a4207f2014-07-31 17:09:05 -0700163import com.android.contacts.quickcontact.ExpandingEntryCardView.EntryContextMenuInfo;
Paul Soulos2ed2a732014-08-12 11:58:39 -0700164import com.android.contacts.quickcontact.ExpandingEntryCardView.EntryTag;
Brian Attwelle8ce6ee2014-06-27 18:26:32 -0700165import com.android.contacts.quickcontact.ExpandingEntryCardView.ExpandingEntryCardViewListener;
Brian Attwell493f8d12014-12-18 12:42:03 -0800166import com.android.contacts.quickcontact.WebAddress.ParseException;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -0700167import com.android.contacts.util.ImageViewDrawableSetter;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700168import com.android.contacts.util.PhoneCapabilityTester;
Brian Attwellb7e43642014-06-02 14:33:04 -0700169import com.android.contacts.util.SchedulingUtils;
Tingting Wangac9596e2016-08-02 22:24:24 -0700170import com.android.contacts.util.SharedPreferenceUtil;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700171import com.android.contacts.util.StructuredPostalUtils;
Brian Attwellb7e43642014-06-02 14:33:04 -0700172import com.android.contacts.widget.MultiShrinkScroller;
173import com.android.contacts.widget.MultiShrinkScroller.MultiShrinkScrollerListener;
Brian Attwell02eaf112014-08-27 21:03:42 -0700174import com.android.contacts.widget.QuickContactImageView;
Brian Attwell56bcc2f2015-02-12 23:45:27 +0000175import com.android.contactsbind.HelpUtils;
Gary Mai678108e2016-10-26 14:34:33 -0700176
Chiao Chenge0b2f1e2012-06-12 13:07:56 -0700177import com.google.common.collect.Lists;
178
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700179import java.util.ArrayList;
Paul Soulos899aa212014-06-11 12:04:43 -0700180import java.util.Arrays;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700181import java.util.Calendar;
Paul Soulosb3054e52014-06-05 16:46:02 -0700182import java.util.Collections;
183import java.util.Comparator;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700184import java.util.Date;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700185import java.util.HashMap;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700186import java.util.List;
Paul Soulosb3054e52014-06-05 16:46:02 -0700187import java.util.Map;
Jay Shrauner12ac1e62014-12-08 15:57:19 -0800188import java.util.concurrent.ConcurrentHashMap;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700189
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700190/**
191 * Mostly translucent {@link Activity} that shows QuickContact dialog. It loads
192 * data asynchronously, and then shows a popup with details centered around
193 * {@link Intent#getSourceBounds()}.
194 */
Gary Maib9065dd2016-11-08 10:49:00 -0800195public class QuickContactActivity extends ContactsActivity {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700196
197 /**
198 * QuickContacts immediately takes up the full screen. All possible information is shown.
199 * This value for {@link android.provider.ContactsContract.QuickContact#EXTRA_MODE}
200 * should only be used by the Contacts app.
201 */
202 public static final int MODE_FULLY_EXPANDED = 4;
203
Walter Jang62a20742016-02-24 17:04:12 -0800204 /** Used to pass the screen where the user came before launching this Activity. */
205 public static final String EXTRA_PREVIOUS_SCREEN_TYPE = "previous_screen_type";
yaolu79525d02016-08-24 12:08:39 -0700206 /** Used to pass the Contact card action. */
207 public static final String EXTRA_ACTION_TYPE = "action_type";
208 public static final String EXTRA_THIRD_PARTY_ACTION = "third_party_action";
209
Gary Mai250fc162016-06-10 16:54:15 -0700210 /** Used to tell the QuickContact that the previous contact was edited, so it can return an
211 * activity result back to the original Activity that launched it. */
212 public static final String EXTRA_CONTACT_EDITED = "contact_edited";
Walter Jang62a20742016-02-24 17:04:12 -0800213
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700214 private static final String TAG = "QuickContact";
215
Brian Attwell9b889e62014-06-23 18:25:32 -0700216 private static final String KEY_THEME_COLOR = "theme_color";
Tingting Wang18906c02015-10-16 14:53:10 -0700217 private static final String KEY_PREVIOUS_CONTACT_ID = "previous_contact_id";
Brian Attwell9b889e62014-06-23 18:25:32 -0700218
yaolu58a1ac22016-10-24 16:50:24 -0700219 private static final String KEY_SEND_TO_VOICE_MAIL_STATE = "sendToVoicemailState";
220 private static final String KEY_ARE_PHONE_OPTIONS_CHANGEABLE = "arePhoneOptionsChangable";
221 private static final String KEY_CUSTOM_RINGTONE = "customRingtone";
222
Brian Attwell9b889e62014-06-23 18:25:32 -0700223 private static final int ANIMATION_STATUS_BAR_COLOR_CHANGE_DURATION = 150;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700224 private static final int REQUEST_CODE_CONTACT_EDITOR_ACTIVITY = 1;
Brian Attwell0b267fe2014-10-24 19:24:40 +0000225 private static final int SCRIM_COLOR = Color.argb(0xC8, 0, 0, 0);
Paul Soulosab54ea12014-08-28 17:22:38 -0700226 private static final int REQUEST_CODE_CONTACT_SELECTION_ACTIVITY = 2;
Paul Soulosea5e0b72014-07-08 18:09:44 -0700227 private static final String MIMETYPE_SMS = "vnd.android-dir/mms-sms";
yaolu2de7c8e2016-09-26 09:45:44 -0700228 private static final int REQUEST_CODE_JOIN = 3;
yaolu58a1ac22016-10-24 16:50:24 -0700229 private static final int REQUEST_CODE_PICK_RINGTONE = 4;
230
231 private static final int CURRENT_API_VERSION = android.os.Build.VERSION.SDK_INT;
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700232
Brian Attwell752cccf2014-06-10 16:24:04 -0700233 /** This is the Intent action to install a shortcut in the launcher. */
234 private static final String ACTION_INSTALL_SHORTCUT =
235 "com.android.launcher.action.INSTALL_SHORTCUT";
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700236
yaolu2de7c8e2016-09-26 09:45:44 -0700237 public static final String ACTION_SPLIT_COMPLETED = "splitCompleted";
238
yaolu58a1ac22016-10-24 16:50:24 -0700239 // Phone specific option menu items
240 private boolean mSendToVoicemailState;
241 private boolean mArePhoneOptionsChangable;
242 private String mCustomRingtone;
243
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700244 @SuppressWarnings("deprecation")
245 private static final String LEGACY_AUTHORITY = android.provider.Contacts.AUTHORITY;
246
Paul Soulose0055662014-07-10 16:33:08 -0700247 private static final String MIMETYPE_GPLUS_PROFILE =
248 "vnd.android.cursor.item/vnd.googleplus.profile";
Paul Soulos6a2a1a72014-10-10 15:52:56 -0700249 private static final String GPLUS_PROFILE_DATA_5_VIEW_PROFILE = "view";
Paul Soulose0055662014-07-10 16:33:08 -0700250 private static final String MIMETYPE_HANGOUTS =
251 "vnd.android.cursor.item/vnd.googleplus.profile.comm";
Paul Soulos6a2a1a72014-10-10 15:52:56 -0700252 private static final String HANGOUTS_DATA_5_VIDEO = "hangout";
253 private static final String HANGOUTS_DATA_5_MESSAGE = "conversation";
Paul Soulos48fc9122014-08-26 13:52:36 -0700254 private static final String CALL_ORIGIN_QUICK_CONTACTS_ACTIVITY =
255 "com.android.contacts.quickcontact.QuickContactActivity";
Paul Soulose0055662014-07-10 16:33:08 -0700256
yaolu79525d02016-08-24 12:08:39 -0700257 // Set true in {@link #onCreate} after orientation change for later use in processIntent().
258 private boolean mIsRecreatedInstance;
Marcus Hagerottd105c1e2016-09-30 14:28:00 -0700259 private boolean mShortcutUsageReported = false;
yaolu79525d02016-08-24 12:08:39 -0700260
261 private boolean mShouldLog;
262
263 // Used to store and log the referrer package name and the contact type.
264 private String mReferrer;
265 private int mContactType;
266
Brian Attwell63176c92014-08-18 15:14:18 -0700267 /**
268 * The URI used to load the the Contact. Once the contact is loaded, use Contact#getLookupUri()
269 * instead of referencing this URI.
270 */
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700271 private Uri mLookupUri;
272 private String[] mExcludeMimes;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700273 private int mExtraMode;
Brian Attwell05287bf2015-02-25 22:24:04 -0800274 private String mExtraPrioritizedMimeType;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700275 private int mStatusBarColor;
276 private boolean mHasAlreadyBeenOpened;
Paul Soulos97f27802014-09-08 13:55:45 -0700277 private boolean mOnlyOnePhoneNumber;
278 private boolean mOnlyOneEmail;
Gary Maib9065dd2016-11-08 10:49:00 -0800279 private ProgressDialog mProgressDialog;
280 private SaveServiceListener mListener;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700281
Brian Attwell02eaf112014-08-27 21:03:42 -0700282 private QuickContactImageView mPhotoView;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700283 private ExpandingEntryCardView mContactCard;
Brian Attwell60953692014-07-11 17:18:46 -0700284 private ExpandingEntryCardView mNoContactDetailsCard;
Paul Soulosb3054e52014-06-05 16:46:02 -0700285 private ExpandingEntryCardView mRecentCard;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700286 private ExpandingEntryCardView mAboutCard;
guanxiongliu04a19362016-05-18 14:38:51 -0700287 private ExpandingEntryCardView mPermissionExplanationCard;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700288
Tingting Wang18906c02015-10-16 14:53:10 -0700289 private long mPreviousContactId = 0;
guanxiongliu04a19362016-05-18 14:38:51 -0700290 // Permission explanation card.
291 private boolean mShouldShowPermissionExplanation = false;
292 private String mPermissionExplanationCardSubHeader = "";
293
Brian Attwellb7e43642014-06-02 14:33:04 -0700294 private MultiShrinkScroller mScroller;
Brian Attwell6bb01342014-08-20 23:16:00 -0700295 private AsyncTask<Void, Void, Cp2DataCardModel> mEntriesAndActionsTask;
Brian Attwell30e1ef12014-09-02 15:49:20 -0700296 private AsyncTask<Void, Void, Void> mRecentDataTask;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700297
Brian Attwell6bb01342014-08-20 23:16:00 -0700298 /**
299 * The last copy of Cp2DataCardModel that was passed to {@link #populateContactAndAboutCard}.
300 */
301 private Cp2DataCardModel mCachedCp2DataCardModel;
Brian Attwellf1076dc2014-08-12 21:28:21 -0700302 /**
303 * This scrim's opacity is controlled in two different ways. 1) Before the initial entrance
304 * animation finishes, the opacity is animated by a value animator. This is designed to
305 * distract the user from the length of the initial loading time. 2) After the initial
306 * entrance animation, the opacity is directly related to scroll position.
307 */
Brian Attwell9b889e62014-06-23 18:25:32 -0700308 private ColorDrawable mWindowScrim;
Brian Attwellf1076dc2014-08-12 21:28:21 -0700309 private boolean mIsEntranceAnimationFinished;
Brian Attwella3859ed2014-07-15 13:51:55 -0700310 private MaterialColorMapUtils mMaterialColorMapUtils;
Brian Attwell8477eaf2014-06-18 15:39:50 -0700311 private boolean mIsExitAnimationInProgress;
Brian Attwell8571dd32014-06-23 23:29:10 -0700312 private boolean mHasComputedThemeColor;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700313
Paul Soulosa42ef762014-08-20 10:26:10 -0700314 /**
315 * Used to stop the ExpandingEntry cards from adjusting between an entry click and the intent
316 * being launched.
317 */
318 private boolean mHasIntentLaunched;
319
Yorke Lee6cb6f6f2013-09-26 16:26:03 -0700320 private Contact mContactData;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700321 private ContactLoader mContactLoader;
Brian Attwell9b889e62014-06-23 18:25:32 -0700322 private PorterDuffColorFilter mColorFilter;
Brian Attwell8e29faf2015-01-21 10:22:40 -0800323 private int mColorFilterColor;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700324
Josh Gargus9758a922012-03-08 17:12:42 -0800325 private final ImageViewDrawableSetter mPhotoSetter = new ImageViewDrawableSetter();
326
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700327 /**
Paul Souloscb4fcc72014-07-15 14:08:50 -0700328 * {@link #LEADING_MIMETYPES} is used to sort MIME-types.
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700329 *
330 * <p>The MIME-types in {@link #LEADING_MIMETYPES} appear in the front of the dialog,
331 * in the order specified here.</p>
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700332 */
333 private static final List<String> LEADING_MIMETYPES = Lists.newArrayList(
Paul Souloscb4fcc72014-07-15 14:08:50 -0700334 Phone.CONTENT_ITEM_TYPE, SipAddress.CONTENT_ITEM_TYPE, Email.CONTENT_ITEM_TYPE,
Paul Soulos405ae402014-07-14 16:16:36 -0700335 StructuredPostal.CONTENT_ITEM_TYPE);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700336
Paul Soulosa153dba2014-08-06 13:44:57 -0700337 private static final List<String> SORTED_ABOUT_CARD_MIMETYPES = Lists.newArrayList(
338 Nickname.CONTENT_ITEM_TYPE,
339 // Phonetic name is inserted after nickname if it is available.
340 // No mimetype for phonetic name exists.
341 Website.CONTENT_ITEM_TYPE,
342 Organization.CONTENT_ITEM_TYPE,
343 Event.CONTENT_ITEM_TYPE,
344 Relation.CONTENT_ITEM_TYPE,
345 Im.CONTENT_ITEM_TYPE,
346 GroupMembership.CONTENT_ITEM_TYPE,
347 Identity.CONTENT_ITEM_TYPE,
yaolu1bd88262016-08-18 10:29:12 -0700348 CustomDataItem.MIMETYPE_CUSTOM_FIELD,
Paul Soulosa153dba2014-08-06 13:44:57 -0700349 Note.CONTENT_ITEM_TYPE);
Paul Souloseb64a4b2014-07-07 17:03:27 -0700350
Brian Attwellc62cc792014-10-02 12:35:07 -0700351 private static final BidiFormatter sBidiFormatter = BidiFormatter.getInstance();
352
Paul Soulosb3054e52014-06-05 16:46:02 -0700353 /** Id for the background contact loader */
354 private static final int LOADER_CONTACT_ID = 0;
355
356 /** Id for the background Sms Loader */
357 private static final int LOADER_SMS_ID = 1;
Paul Soulosb3054e52014-06-05 16:46:02 -0700358 private static final int MAX_SMS_RETRIEVE = 3;
Paul Soulosab840442014-06-17 14:08:40 -0700359
360 /** Id for the back Calendar Loader */
Paul Soulos899aa212014-06-11 12:04:43 -0700361 private static final int LOADER_CALENDAR_ID = 2;
Paul Soulosab840442014-06-17 14:08:40 -0700362 private static final String KEY_LOADER_EXTRA_EMAILS =
363 QuickContactActivity.class.getCanonicalName() + ".KEY_LOADER_EXTRA_EMAILS";
Paul Soulos899aa212014-06-11 12:04:43 -0700364 private static final int MAX_PAST_CALENDAR_RETRIEVE = 3;
365 private static final int MAX_FUTURE_CALENDAR_RETRIEVE = 3;
366 private static final long PAST_MILLISECOND_TO_SEARCH_LOCAL_CALENDAR =
Paul Soulos40d7a652014-09-03 13:54:11 -0700367 1L * 24L * 60L * 60L * 1000L /* 1 day */;
Paul Soulos899aa212014-06-11 12:04:43 -0700368 private static final long FUTURE_MILLISECOND_TO_SEARCH_LOCAL_CALENDAR =
Paul Soulos40d7a652014-09-03 13:54:11 -0700369 7L * 24L * 60L * 60L * 1000L /* 7 days */;
Paul Soulosb3054e52014-06-05 16:46:02 -0700370
Paul Soulosab840442014-06-17 14:08:40 -0700371 /** Id for the background Call Log Loader */
372 private static final int LOADER_CALL_LOG_ID = 3;
373 private static final int MAX_CALL_LOG_RETRIEVE = 3;
Brian Attwell60953692014-07-11 17:18:46 -0700374 private static final int MIN_NUM_CONTACT_ENTRIES_SHOWN = 3;
375 private static final int MIN_NUM_COLLAPSED_RECENT_ENTRIES_SHOWN = 3;
376 private static final int CARD_ENTRY_ID_EDIT_CONTACT = -2;
guanxiongliu04a19362016-05-18 14:38:51 -0700377 private static final int CARD_ENTRY_ID_REQUEST_PERMISSION = -3;
guanxiongliuc7a4b9c2016-04-30 20:19:21 -0700378 private static final String KEY_LOADER_EXTRA_PHONES =
379 QuickContactActivity.class.getCanonicalName() + ".KEY_LOADER_EXTRA_PHONES";
380 private static final String KEY_LOADER_EXTRA_SIP_NUMBERS =
381 QuickContactActivity.class.getCanonicalName() + ".KEY_LOADER_EXTRA_SIP_NUMBERS";
Paul Soulosab840442014-06-17 14:08:40 -0700382
383 private static final int[] mRecentLoaderIds = new int[]{
384 LOADER_SMS_ID,
385 LOADER_CALENDAR_ID,
386 LOADER_CALL_LOG_ID};
Jay Shrauner12ac1e62014-12-08 15:57:19 -0800387 /**
388 * ConcurrentHashMap constructor params: 4 is initial table size, 0.9f is
389 * load factor before resizing, 1 means we only expect a single thread to
390 * write to the map so make only a single shard
391 */
392 private Map<Integer, List<ContactInteraction>> mRecentLoaderResults =
393 new ConcurrentHashMap<>(4, 0.9f, 1);
Paul Soulosb3054e52014-06-05 16:46:02 -0700394
Brian Attwelld28851f2014-06-10 13:25:07 -0700395 private static final String FRAGMENT_TAG_SELECT_ACCOUNT = "select_account_fragment";
Yorke Lee6cb6f6f2013-09-26 16:26:03 -0700396
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700397 final OnClickListener mEntryClickHandler = new OnClickListener() {
398 @Override
399 public void onClick(View v) {
Paul Soulos2ed2a732014-08-12 11:58:39 -0700400 final Object entryTagObject = v.getTag();
401 if (entryTagObject == null || !(entryTagObject instanceof EntryTag)) {
402 Log.w(TAG, "EntryTag was not used correctly");
403 return;
404 }
405 final EntryTag entryTag = (EntryTag) entryTagObject;
406 final Intent intent = entryTag.getIntent();
407 final int dataId = entryTag.getId();
408
Brian Attwell60953692014-07-11 17:18:46 -0700409 if (dataId == CARD_ENTRY_ID_EDIT_CONTACT) {
410 editContact();
411 return;
412 }
Paul Soulosea5e0b72014-07-08 18:09:44 -0700413
guanxiongliu04a19362016-05-18 14:38:51 -0700414 if (dataId == CARD_ENTRY_ID_REQUEST_PERMISSION) {
415 finish();
416 RequestDesiredPermissionsActivity.startPermissionActivity(
417 QuickContactActivity.this);
418 return;
419 }
420
LIANGTAO GAO19c83e32015-03-12 13:32:04 -0700421 // Pass the touch point through the intent for use in the InCallUI
422 if (Intent.ACTION_CALL.equals(intent.getAction())) {
423 if (TouchPointManager.getInstance().hasValidPoint()) {
424 Bundle extras = new Bundle();
425 extras.putParcelable(TouchPointManager.TOUCH_POINT,
426 TouchPointManager.getInstance().getPoint());
427 intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
428 }
429 }
430
431 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
432
433 mHasIntentLaunched = true;
434 try {
yaolu79525d02016-08-24 12:08:39 -0700435 final int actionType = intent.getIntExtra(EXTRA_ACTION_TYPE,
436 ActionType.UNKNOWN_ACTION);
437 final String thirdPartyAction = intent.getStringExtra(EXTRA_THIRD_PARTY_ACTION);
438 Logger.logQuickContactEvent(mReferrer, mContactType,
439 CardType.UNKNOWN_CARD, actionType, thirdPartyAction);
Tingting Wang8c434e52016-01-29 16:45:09 -0800440 ImplicitIntentsUtil.startActivityInAppIfPossible(QuickContactActivity.this, intent);
LIANGTAO GAO19c83e32015-03-12 13:32:04 -0700441 } catch (SecurityException ex) {
442 Toast.makeText(QuickContactActivity.this, R.string.missing_app,
443 Toast.LENGTH_SHORT).show();
444 Log.e(TAG, "QuickContacts does not have permission to launch "
445 + intent);
446 } catch (ActivityNotFoundException ex) {
447 Toast.makeText(QuickContactActivity.this, R.string.missing_app,
448 Toast.LENGTH_SHORT).show();
449 }
450
Paul Soulosea5e0b72014-07-08 18:09:44 -0700451 // Default to USAGE_TYPE_CALL. Usage is summed among all types for sorting each data id
452 // so the exact usage type is not necessary in all cases
453 String usageType = DataUsageFeedback.USAGE_TYPE_CALL;
454
Paul Soulos20bdf9d2014-07-28 14:31:54 -0700455 final Uri intentUri = intent.getData();
456 if ((intentUri != null && intentUri.getScheme() != null &&
Jay Shrauner1cd88e32014-09-05 15:37:55 -0700457 intentUri.getScheme().equals(ContactsUtils.SCHEME_SMSTO)) ||
Paul Soulosea5e0b72014-07-08 18:09:44 -0700458 (intent.getType() != null && intent.getType().equals(MIMETYPE_SMS))) {
459 usageType = DataUsageFeedback.USAGE_TYPE_SHORT_TEXT;
460 }
461
462 // Data IDs start at 1 so anything less is invalid
463 if (dataId > 0) {
Paul Soulos20bdf9d2014-07-28 14:31:54 -0700464 final Uri dataUsageUri = DataUsageFeedback.FEEDBACK_URI.buildUpon()
Paul Soulosea5e0b72014-07-08 18:09:44 -0700465 .appendPath(String.valueOf(dataId))
466 .appendQueryParameter(DataUsageFeedback.USAGE_TYPE, usageType)
467 .build();
Jay Shrauner98818822015-07-10 12:54:10 -0700468 try {
469 final boolean successful = getContentResolver().update(
470 dataUsageUri, new ContentValues(), null, null) > 0;
471 if (!successful) {
472 Log.w(TAG, "DataUsageFeedback increment failed");
473 }
474 } catch (SecurityException ex) {
475 Log.w(TAG, "DataUsageFeedback increment failed", ex);
Paul Soulosea5e0b72014-07-08 18:09:44 -0700476 }
477 } else {
478 Log.w(TAG, "Invalid Data ID");
479 }
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700480 }
481 };
482
Brian Attwelle8ce6ee2014-06-27 18:26:32 -0700483 final ExpandingEntryCardViewListener mExpandingEntryCardViewListener
484 = new ExpandingEntryCardViewListener() {
485 @Override
486 public void onCollapse(int heightDelta) {
487 mScroller.prepareForShrinkingScrollChild(heightDelta);
488 }
Paul Soulos0cda9ae2014-07-23 11:27:28 -0700489
490 @Override
Brian Attwell245d3d22015-01-21 09:50:08 -0800491 public void onExpand() {
492 mScroller.setDisableTouchesForSuppressLayout(/* areTouchesDisabled = */ true);
493 }
494
495 @Override
496 public void onExpandDone() {
497 mScroller.setDisableTouchesForSuppressLayout(/* areTouchesDisabled = */ false);
Paul Soulos0cda9ae2014-07-23 11:27:28 -0700498 }
Brian Attwelle8ce6ee2014-06-27 18:26:32 -0700499 };
500
Paul Soulos97f27802014-09-08 13:55:45 -0700501 private interface ContextMenuIds {
502 static final int COPY_TEXT = 0;
503 static final int CLEAR_DEFAULT = 1;
504 static final int SET_DEFAULT = 2;
505 }
506
Paul Soulos2a4207f2014-07-31 17:09:05 -0700507 private final OnCreateContextMenuListener mEntryContextMenuListener =
508 new OnCreateContextMenuListener() {
509 @Override
510 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
511 if (menuInfo == null) {
512 return;
513 }
Paul Soulos97f27802014-09-08 13:55:45 -0700514 final EntryContextMenuInfo info = (EntryContextMenuInfo) menuInfo;
Paul Soulos2a4207f2014-07-31 17:09:05 -0700515 menu.setHeaderTitle(info.getCopyText());
Paul Soulos97f27802014-09-08 13:55:45 -0700516 menu.add(ContextMenu.NONE, ContextMenuIds.COPY_TEXT,
517 ContextMenu.NONE, getString(R.string.copy_text));
518
519 // Don't allow setting or clearing of defaults for non-editable contacts
520 if (!isContactEditable()) {
521 return;
522 }
523
524 final String selectedMimeType = info.getMimeType();
525
526 // Defaults to true will only enable the detail to be copied to the clipboard.
527 boolean onlyOneOfMimeType = true;
528
529 // Only allow primary support for Phone and Email content types
530 if (Phone.CONTENT_ITEM_TYPE.equals(selectedMimeType)) {
531 onlyOneOfMimeType = mOnlyOnePhoneNumber;
532 } else if (Email.CONTENT_ITEM_TYPE.equals(selectedMimeType)) {
533 onlyOneOfMimeType = mOnlyOneEmail;
534 }
535
536 // Checking for previously set default
537 if (info.isSuperPrimary()) {
538 menu.add(ContextMenu.NONE, ContextMenuIds.CLEAR_DEFAULT,
539 ContextMenu.NONE, getString(R.string.clear_default));
540 } else if (!onlyOneOfMimeType) {
541 menu.add(ContextMenu.NONE, ContextMenuIds.SET_DEFAULT,
542 ContextMenu.NONE, getString(R.string.set_default));
543 }
Paul Soulos2a4207f2014-07-31 17:09:05 -0700544 }
545 };
546
547 @Override
548 public boolean onContextItemSelected(MenuItem item) {
549 EntryContextMenuInfo menuInfo;
550 try {
551 menuInfo = (EntryContextMenuInfo) item.getMenuInfo();
552 } catch (ClassCastException e) {
553 Log.e(TAG, "bad menuInfo", e);
554 return false;
555 }
556
Paul Soulos97f27802014-09-08 13:55:45 -0700557 switch (item.getItemId()) {
558 case ContextMenuIds.COPY_TEXT:
559 ClipboardUtils.copyText(this, menuInfo.getCopyLabel(), menuInfo.getCopyText(),
560 true);
561 return true;
562 case ContextMenuIds.SET_DEFAULT:
563 final Intent setIntent = ContactSaveService.createSetSuperPrimaryIntent(this,
564 menuInfo.getId());
565 this.startService(setIntent);
566 return true;
567 case ContextMenuIds.CLEAR_DEFAULT:
568 final Intent clearIntent = ContactSaveService.createClearPrimaryIntent(this,
569 menuInfo.getId());
570 this.startService(clearIntent);
571 return true;
572 default:
573 throw new IllegalArgumentException("Unknown menu option " + item.getItemId());
574 }
Paul Soulos2a4207f2014-07-31 17:09:05 -0700575 }
576
Brian Attwellb7e43642014-06-02 14:33:04 -0700577 final MultiShrinkScrollerListener mMultiShrinkScrollerListener
578 = new MultiShrinkScrollerListener() {
579 @Override
580 public void onScrolledOffBottom() {
Brian Attwellf1076dc2014-08-12 21:28:21 -0700581 finish();
Brian Attwellb7e43642014-06-02 14:33:04 -0700582 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700583
584 @Override
585 public void onEnterFullscreen() {
586 updateStatusBarColor();
587 }
588
589 @Override
590 public void onExitFullscreen() {
591 updateStatusBarColor();
592 }
Brian Attwell8477eaf2014-06-18 15:39:50 -0700593
594 @Override
595 public void onStartScrollOffBottom() {
Brian Attwell8477eaf2014-06-18 15:39:50 -0700596 mIsExitAnimationInProgress = true;
597 }
Brian Attwell8477eaf2014-06-18 15:39:50 -0700598
Brian Attwell8477eaf2014-06-18 15:39:50 -0700599 @Override
Brian Attwellf1076dc2014-08-12 21:28:21 -0700600 public void onEntranceAnimationDone() {
601 mIsEntranceAnimationFinished = true;
602 }
603
604 @Override
605 public void onTransparentViewHeightChange(float ratio) {
606 if (mIsEntranceAnimationFinished) {
607 mWindowScrim.setAlpha((int) (0xFF * ratio));
Brian Attwell8477eaf2014-06-18 15:39:50 -0700608 }
Brian Attwell8477eaf2014-06-18 15:39:50 -0700609 }
Brian Attwellb7e43642014-06-02 14:33:04 -0700610 };
611
Paul Souloseb64a4b2014-07-07 17:03:27 -0700612
613 /**
614 * Data items are compared to the same mimetype based off of three qualities:
615 * 1. Super primary
616 * 2. Primary
617 * 3. Times used
618 */
619 private final Comparator<DataItem> mWithinMimeTypeDataItemComparator =
620 new Comparator<DataItem>() {
621 @Override
622 public int compare(DataItem lhs, DataItem rhs) {
623 if (!lhs.getMimeType().equals(rhs.getMimeType())) {
624 Log.wtf(TAG, "Comparing DataItems with different mimetypes lhs.getMimeType(): " +
625 lhs.getMimeType() + " rhs.getMimeType(): " + rhs.getMimeType());
626 return 0;
627 }
628
629 if (lhs.isSuperPrimary()) {
630 return -1;
631 } else if (rhs.isSuperPrimary()) {
632 return 1;
633 } else if (lhs.isPrimary() && !rhs.isPrimary()) {
634 return -1;
635 } else if (!lhs.isPrimary() && rhs.isPrimary()) {
636 return 1;
637 } else {
638 final int lhsTimesUsed =
639 lhs.getTimesUsed() == null ? 0 : lhs.getTimesUsed();
640 final int rhsTimesUsed =
641 rhs.getTimesUsed() == null ? 0 : rhs.getTimesUsed();
642
643 return rhsTimesUsed - lhsTimesUsed;
644 }
645 }
646 };
647
Paul Souloscb4fcc72014-07-15 14:08:50 -0700648 /**
649 * Sorts among different mimetypes based off:
Brian Attwell05287bf2015-02-25 22:24:04 -0800650 * 1. Whether one of the mimetypes is the prioritized mimetype
651 * 2. Number of times used
652 * 3. Last time used
653 * 4. Statically defined
Paul Souloscb4fcc72014-07-15 14:08:50 -0700654 */
Paul Souloseb64a4b2014-07-07 17:03:27 -0700655 private final Comparator<List<DataItem>> mAmongstMimeTypeDataItemComparator =
656 new Comparator<List<DataItem>> () {
657 @Override
658 public int compare(List<DataItem> lhsList, List<DataItem> rhsList) {
Brian Attwell05287bf2015-02-25 22:24:04 -0800659 final DataItem lhs = lhsList.get(0);
660 final DataItem rhs = rhsList.get(0);
661 final String lhsMimeType = lhs.getMimeType();
662 final String rhsMimeType = rhs.getMimeType();
663
664 // 1. Whether one of the mimetypes is the prioritized mimetype
665 if (!TextUtils.isEmpty(mExtraPrioritizedMimeType) && !lhsMimeType.equals(rhsMimeType)) {
666 if (rhsMimeType.equals(mExtraPrioritizedMimeType)) {
667 return 1;
668 }
669 if (lhsMimeType.equals(mExtraPrioritizedMimeType)) {
670 return -1;
671 }
672 }
673
674 // 2. Number of times used
Paul Souloseb64a4b2014-07-07 17:03:27 -0700675 final int lhsTimesUsed = lhs.getTimesUsed() == null ? 0 : lhs.getTimesUsed();
676 final int rhsTimesUsed = rhs.getTimesUsed() == null ? 0 : rhs.getTimesUsed();
677 final int timesUsedDifference = rhsTimesUsed - lhsTimesUsed;
678 if (timesUsedDifference != 0) {
679 return timesUsedDifference;
680 }
681
Brian Attwell05287bf2015-02-25 22:24:04 -0800682 // 3. Last time used
Paul Souloseb64a4b2014-07-07 17:03:27 -0700683 final long lhsLastTimeUsed =
684 lhs.getLastTimeUsed() == null ? 0 : lhs.getLastTimeUsed();
685 final long rhsLastTimeUsed =
686 rhs.getLastTimeUsed() == null ? 0 : rhs.getLastTimeUsed();
687 final long lastTimeUsedDifference = rhsLastTimeUsed - lhsLastTimeUsed;
688 if (lastTimeUsedDifference > 0) {
689 return 1;
690 } else if (lastTimeUsedDifference < 0) {
691 return -1;
692 }
693
Brian Attwell05287bf2015-02-25 22:24:04 -0800694 // 4. Resort to a statically defined mimetype order.
695 if (!lhsMimeType.equals(rhsMimeType)) {
696 for (String mimeType : LEADING_MIMETYPES) {
697 if (lhsMimeType.equals(mimeType)) {
698 return -1;
699 } else if (rhsMimeType.equals(mimeType)) {
700 return 1;
701 }
Paul Souloseb64a4b2014-07-07 17:03:27 -0700702 }
703 }
Paul Souloseb64a4b2014-07-07 17:03:27 -0700704 return 0;
705 }
706 };
707
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700708 @Override
Nancy Chenf619e432014-08-18 20:15:12 -0700709 public boolean dispatchTouchEvent(MotionEvent ev) {
710 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
711 TouchPointManager.getInstance().setPoint((int) ev.getRawX(), (int) ev.getRawY());
712 }
713 return super.dispatchTouchEvent(ev);
714 }
715
716 @Override
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700717 protected void onCreate(Bundle savedInstanceState) {
718 Trace.beginSection("onCreate()");
719 super.onCreate(savedInstanceState);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700720
guanxiongliu04a19362016-05-18 14:38:51 -0700721 if (RequestPermissionsActivity.startPermissionActivity(this)) {
Brian Attwellbdd32642015-05-08 17:03:15 -0700722 return;
723 }
724
yaolu7a09c702016-09-01 18:12:03 -0700725 mIsRecreatedInstance = savedInstanceState != null;
yaolu2de7c8e2016-09-26 09:45:44 -0700726 if (mIsRecreatedInstance) {
727 mPreviousContactId = savedInstanceState.getLong(KEY_PREVIOUS_CONTACT_ID);
yaolu58a1ac22016-10-24 16:50:24 -0700728
729 // Phone specific options menus
730 mSendToVoicemailState = savedInstanceState.getBoolean(KEY_SEND_TO_VOICE_MAIL_STATE);
731 mArePhoneOptionsChangable =
732 savedInstanceState.getBoolean(KEY_ARE_PHONE_OPTIONS_CHANGEABLE);
733 mCustomRingtone = savedInstanceState.getString(KEY_CUSTOM_RINGTONE);
yaolu2de7c8e2016-09-26 09:45:44 -0700734 }
Gary Maib9065dd2016-11-08 10:49:00 -0800735 mProgressDialog = new ProgressDialog(this);
736 mProgressDialog.setIndeterminate(true);
737 mProgressDialog.setCancelable(false);
738
739 mListener = new SaveServiceListener();
740 final IntentFilter intentFilter = new IntentFilter();
741 intentFilter.addAction(ContactSaveService.BROADCAST_LINK_COMPLETE);
742 intentFilter.addAction(ContactSaveService.BROADCAST_UNLINK_COMPLETE);
743 LocalBroadcastManager.getInstance(this).registerReceiver(mListener,
744 intentFilter);
745
746
yaolu79525d02016-08-24 12:08:39 -0700747 mShouldLog = true;
748
guanxiongliu04a19362016-05-18 14:38:51 -0700749 // There're 3 states for each permission:
750 // 1. App doesn't have permission, not asked user yet.
751 // 2. App doesn't have permission, user denied it previously.
752 // 3. App has permission.
753 // Permission explanation card is displayed only for case 1.
Walter Jang5a16ef82016-07-27 09:20:59 -0700754 final boolean hasTelephonyFeature =
755 getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
756
guanxiongliu04a19362016-05-18 14:38:51 -0700757 final boolean hasCalendarPermission = PermissionsUtil.hasPermission(
Walter Jang0e723d92016-07-07 19:00:30 -0700758 this, Manifest.permission.READ_CALENDAR);
Walter Jang5a16ef82016-07-27 09:20:59 -0700759 final boolean hasSMSPermission = hasTelephonyFeature
760 && PermissionsUtil.hasPermission(this, Manifest.permission.READ_SMS);
guanxiongliu04a19362016-05-18 14:38:51 -0700761
762 final boolean wasCalendarPermissionDenied =
763 ActivityCompat.shouldShowRequestPermissionRationale(
Walter Jang0e723d92016-07-07 19:00:30 -0700764 this, Manifest.permission.READ_CALENDAR);
guanxiongliu04a19362016-05-18 14:38:51 -0700765 final boolean wasSMSPermissionDenied =
Walter Jang5a16ef82016-07-27 09:20:59 -0700766 hasTelephonyFeature && ActivityCompat.shouldShowRequestPermissionRationale(
Walter Jang0e723d92016-07-07 19:00:30 -0700767 this, Manifest.permission.READ_SMS);
guanxiongliu04a19362016-05-18 14:38:51 -0700768
769 final boolean shouldDisplayCalendarMessage =
770 !hasCalendarPermission && !wasCalendarPermissionDenied;
Walter Jang5a16ef82016-07-27 09:20:59 -0700771 final boolean shouldDisplaySMSMessage =
772 hasTelephonyFeature && !hasSMSPermission && !wasSMSPermissionDenied;
guanxiongliu04a19362016-05-18 14:38:51 -0700773 mShouldShowPermissionExplanation = shouldDisplayCalendarMessage || shouldDisplaySMSMessage;
774
775 if (shouldDisplayCalendarMessage && shouldDisplaySMSMessage) {
776 mPermissionExplanationCardSubHeader =
777 getString(R.string.permission_explanation_subheader_calendar_and_SMS);
778 } else if (shouldDisplayCalendarMessage) {
779 mPermissionExplanationCardSubHeader =
780 getString(R.string.permission_explanation_subheader_calendar);
781 } else if (shouldDisplaySMSMessage) {
782 mPermissionExplanationCardSubHeader =
783 getString(R.string.permission_explanation_subheader_SMS);
784 }
785
Walter Jang1bb6c292016-02-29 11:46:03 -0800786 final int previousScreenType = getIntent().getIntExtra
787 (EXTRA_PREVIOUS_SCREEN_TYPE, ScreenType.UNKNOWN);
Walter Jang62a20742016-02-24 17:04:12 -0800788 Logger.logScreenView(this, ScreenType.QUICK_CONTACT, previousScreenType);
789
yaolu79525d02016-08-24 12:08:39 -0700790 mReferrer = getCallingPackage();
791 if (mReferrer == null && CompatUtils.isLollipopMr1Compatible() && getReferrer() != null) {
792 mReferrer = getReferrer().getAuthority();
793 }
794 mContactType = ContactType.UNKNOWN_TYPE;
795
Wenyi Wang4c3d3e22015-12-17 14:30:02 -0800796 if (CompatUtils.isLollipopCompatible()) {
797 getWindow().setStatusBarColor(Color.TRANSPARENT);
798 }
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700799
Brian Attwelld28851f2014-06-10 13:25:07 -0700800 processIntent(getIntent());
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700801
Daisuke Miyakawa10d7df72011-07-29 16:11:05 -0700802 // Show QuickContact in front of soft input
803 getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
804 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
805
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700806 setContentView(R.layout.quickcontact_activity);
807
Brian Attwella3859ed2014-07-15 13:51:55 -0700808 mMaterialColorMapUtils = new MaterialColorMapUtils(getResources());
809
Paul Soulos0cda9ae2014-07-23 11:27:28 -0700810 mScroller = (MultiShrinkScroller) findViewById(R.id.multiscroller);
811
Paul Souloseb64a4b2014-07-07 17:03:27 -0700812 mContactCard = (ExpandingEntryCardView) findViewById(R.id.communication_card);
Brian Attwell60953692014-07-11 17:18:46 -0700813 mNoContactDetailsCard = (ExpandingEntryCardView) findViewById(R.id.no_contact_data_card);
Paul Soulosb3054e52014-06-05 16:46:02 -0700814 mRecentCard = (ExpandingEntryCardView) findViewById(R.id.recent_card);
Paul Souloseb64a4b2014-07-07 17:03:27 -0700815 mAboutCard = (ExpandingEntryCardView) findViewById(R.id.about_card);
guanxiongliu04a19362016-05-18 14:38:51 -0700816 mPermissionExplanationCard =
817 (ExpandingEntryCardView) findViewById(R.id.permission_explanation_card);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700818
guanxiongliu04a19362016-05-18 14:38:51 -0700819 mPermissionExplanationCard.setOnClickListener(mEntryClickHandler);
Brian Attwell60953692014-07-11 17:18:46 -0700820 mNoContactDetailsCard.setOnClickListener(mEntryClickHandler);
Paul Souloseb64a4b2014-07-07 17:03:27 -0700821 mContactCard.setOnClickListener(mEntryClickHandler);
Paul Soulos2a4207f2014-07-31 17:09:05 -0700822 mContactCard.setOnCreateContextMenuListener(mEntryContextMenuListener);
Paul Soulosb3054e52014-06-05 16:46:02 -0700823
824 mRecentCard.setOnClickListener(mEntryClickHandler);
825 mRecentCard.setTitle(getResources().getString(R.string.recent_card_title));
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700826
Paul Souloseb64a4b2014-07-07 17:03:27 -0700827 mAboutCard.setOnClickListener(mEntryClickHandler);
Paul Soulos2a4207f2014-07-31 17:09:05 -0700828 mAboutCard.setOnCreateContextMenuListener(mEntryContextMenuListener);
Paul Souloseb64a4b2014-07-07 17:03:27 -0700829
Brian Attwell02eaf112014-08-27 21:03:42 -0700830 mPhotoView = (QuickContactImageView) findViewById(R.id.photo);
Brian Attwell63176c92014-08-18 15:14:18 -0700831 final View transparentView = findViewById(R.id.transparent_view);
Brian Attwell0d90afe2014-06-18 16:45:41 -0700832 if (mScroller != null) {
Brian Attwell63176c92014-08-18 15:14:18 -0700833 transparentView.setOnClickListener(new OnClickListener() {
Brian Attwell0d90afe2014-06-18 16:45:41 -0700834 @Override
835 public void onClick(View v) {
836 mScroller.scrollOffBottom();
837 }
838 });
839 }
Yorke Lee62196682013-09-06 18:34:17 -0700840
Brian Attwelld41ab8a2014-08-07 11:08:55 -0700841 // Allow a shadow to be shown under the toolbar.
842 ViewUtil.addRectangularOutlineProvider(findViewById(R.id.toolbar_parent), getResources());
843
Brian Attwelld28851f2014-06-10 13:25:07 -0700844 final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
845 setActionBar(toolbar);
Brian Attwell9b889e62014-06-23 18:25:32 -0700846 getActionBar().setTitle(null);
847 // Put a TextView with a known resource id into the ActionBar. This allows us to easily
848 // find the correct TextView location & size later.
849 toolbar.addView(getLayoutInflater().inflate(R.layout.quickcontact_title_placeholder, null));
Yorke Lee62196682013-09-06 18:34:17 -0700850
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700851 mHasAlreadyBeenOpened = savedInstanceState != null;
Brian Attwellf1076dc2014-08-12 21:28:21 -0700852 mIsEntranceAnimationFinished = mHasAlreadyBeenOpened;
Brian Attwell9b889e62014-06-23 18:25:32 -0700853 mWindowScrim = new ColorDrawable(SCRIM_COLOR);
Brian Attwellf1076dc2014-08-12 21:28:21 -0700854 mWindowScrim.setAlpha(0);
Brian Attwell9b889e62014-06-23 18:25:32 -0700855 getWindow().setBackgroundDrawable(mWindowScrim);
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700856
Wenyi Wangcaf26192016-05-09 15:00:25 -0700857 mScroller.initialize(mMultiShrinkScrollerListener, mExtraMode == MODE_FULLY_EXPANDED,
858 /* maximumHeaderTextSize */ -1,
859 /* shouldUpdateNameViewHeight */ true);
Brian Attwellac3ed8e2014-06-27 17:24:42 -0700860 // mScroller needs to perform asynchronous measurements after initalize(), therefore
861 // we can't mark this as GONE.
862 mScroller.setVisibility(View.INVISIBLE);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700863
Brian Attwell9b889e62014-06-23 18:25:32 -0700864 setHeaderNameText(R.string.missing_name);
865
Brian Attwellf1076dc2014-08-12 21:28:21 -0700866 SchedulingUtils.doOnPreDraw(mScroller, /* drawNextFrame = */ true,
867 new Runnable() {
868 @Override
869 public void run() {
870 if (!mHasAlreadyBeenOpened) {
871 // The initial scrim opacity must match the scrim opacity that would be
872 // achieved by scrolling to the starting position.
873 final float alphaRatio = mExtraMode == MODE_FULLY_EXPANDED ?
874 1 : mScroller.getStartingTransparentHeightRatio();
875 final int duration = getResources().getInteger(
876 android.R.integer.config_shortAnimTime);
877 final int desiredAlpha = (int) (0xFF * alphaRatio);
878 ObjectAnimator o = ObjectAnimator.ofInt(mWindowScrim, "alpha", 0,
879 desiredAlpha).setDuration(duration);
880
881 o.start();
882 }
883 }
884 });
885
Brian Attwell9b889e62014-06-23 18:25:32 -0700886 if (savedInstanceState != null) {
887 final int color = savedInstanceState.getInt(KEY_THEME_COLOR, 0);
Brian Attwellac3ed8e2014-06-27 17:24:42 -0700888 SchedulingUtils.doOnPreDraw(mScroller, /* drawNextFrame = */ false,
889 new Runnable() {
890 @Override
891 public void run() {
892 // Need to wait for the pre draw before setting the initial scroll
893 // value. Prior to pre draw all scroll values are invalid.
894 if (mHasAlreadyBeenOpened) {
895 mScroller.setVisibility(View.VISIBLE);
896 mScroller.setScroll(mScroller.getScrollNeededToBeFullScreen());
897 }
898 // Need to wait for pre draw for setting the theme color. Setting the
899 // header tint before the MultiShrinkScroller has been measured will
900 // cause incorrect tinting calculations.
901 if (color != 0) {
Brian Attwella3859ed2014-07-15 13:51:55 -0700902 setThemeColor(mMaterialColorMapUtils
903 .calculatePrimaryAndSecondaryColor(color));
Brian Attwell9b889e62014-06-23 18:25:32 -0700904 }
Brian Attwellac3ed8e2014-06-27 17:24:42 -0700905 }
906 });
Brian Attwell9b889e62014-06-23 18:25:32 -0700907 }
908
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700909 Trace.endSection();
910 }
911
Paul Soulos405ae402014-07-14 16:16:36 -0700912 @Override
913 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Wenyi Wangd1cb9042015-11-05 17:17:59 -0800914 final boolean deletedOrSplit = requestCode == REQUEST_CODE_CONTACT_EDITOR_ACTIVITY &&
915 (resultCode == ContactDeletionInteraction.RESULT_CODE_DELETED ||
Gary Mai363af602016-09-28 10:01:23 -0700916 resultCode == ContactEditorActivity.RESULT_CODE_SPLIT);
Gary Mai0d76abc2016-05-25 16:19:45 -0700917 setResult(resultCode, data);
Wenyi Wangd1cb9042015-11-05 17:17:59 -0800918 if (deletedOrSplit) {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700919 finish();
Paul Soulosab54ea12014-08-28 17:22:38 -0700920 } else if (requestCode == REQUEST_CODE_CONTACT_SELECTION_ACTIVITY &&
921 resultCode != RESULT_CANCELED) {
922 processIntent(data);
yaolu2de7c8e2016-09-26 09:45:44 -0700923 } else if (requestCode == REQUEST_CODE_JOIN) {
924 // Ignore failed requests
925 if (resultCode != Activity.RESULT_OK) {
yaoluf83db432016-11-09 15:56:11 -0800926 return;
yaolu2de7c8e2016-09-26 09:45:44 -0700927 }
928 if (data != null) {
929 joinAggregate(ContentUris.parseId(data.getData()));
930 }
yaolu58a1ac22016-10-24 16:50:24 -0700931 } else if (requestCode == REQUEST_CODE_PICK_RINGTONE && data != null) {
932 final Uri pickedUri = data.getParcelableExtra(
933 RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
934 onRingtonePicked(pickedUri);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700935 }
936 }
937
yaolu58a1ac22016-10-24 16:50:24 -0700938 private void onRingtonePicked(Uri pickedUri) {
939 mCustomRingtone = EditorUiUtils.getRingtoneStringFromUri(pickedUri, CURRENT_API_VERSION);
940 Intent intent = ContactSaveService.createSetRingtone(
941 this, mLookupUri, mCustomRingtone);
942 this.startService(intent);
943 }
944
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700945 @Override
946 protected void onNewIntent(Intent intent) {
947 super.onNewIntent(intent);
948 mHasAlreadyBeenOpened = true;
Brian Attwellf1076dc2014-08-12 21:28:21 -0700949 mIsEntranceAnimationFinished = true;
Brian Attwell8571dd32014-06-23 23:29:10 -0700950 mHasComputedThemeColor = false;
Brian Attwelld28851f2014-06-10 13:25:07 -0700951 processIntent(intent);
952 }
953
Brian Attwell9b889e62014-06-23 18:25:32 -0700954 @Override
955 public void onSaveInstanceState(Bundle savedInstanceState) {
956 super.onSaveInstanceState(savedInstanceState);
957 if (mColorFilter != null) {
Brian Attwell8e29faf2015-01-21 10:22:40 -0800958 savedInstanceState.putInt(KEY_THEME_COLOR, mColorFilterColor);
Brian Attwell9b889e62014-06-23 18:25:32 -0700959 }
Tingting Wang18906c02015-10-16 14:53:10 -0700960 savedInstanceState.putLong(KEY_PREVIOUS_CONTACT_ID, mPreviousContactId);
yaolu58a1ac22016-10-24 16:50:24 -0700961
962 // Phone specific options
963 savedInstanceState.putBoolean(KEY_SEND_TO_VOICE_MAIL_STATE, mSendToVoicemailState);
964 savedInstanceState.putBoolean(KEY_ARE_PHONE_OPTIONS_CHANGEABLE, mArePhoneOptionsChangable);
965 savedInstanceState.putString(KEY_CUSTOM_RINGTONE, mCustomRingtone);
Brian Attwell9b889e62014-06-23 18:25:32 -0700966 }
967
Brian Attwelld28851f2014-06-10 13:25:07 -0700968 private void processIntent(Intent intent) {
Paul Soulosf19dda92014-09-15 15:48:02 -0700969 if (intent == null) {
970 finish();
971 return;
972 }
yaolu2de7c8e2016-09-26 09:45:44 -0700973 if (ACTION_SPLIT_COMPLETED.equals(intent.getAction())) {
974 Toast.makeText(this, R.string.contactUnlinkedToast, Toast.LENGTH_SHORT).show();
975 finish();
976 return;
977 }
978
Brian Attwelld28851f2014-06-10 13:25:07 -0700979 Uri lookupUri = intent.getData();
Gary Mai250fc162016-06-10 16:54:15 -0700980 if (intent.getBooleanExtra(EXTRA_CONTACT_EDITED, false)) {
Gary Mai363af602016-09-28 10:01:23 -0700981 setResult(ContactEditorActivity.RESULT_CODE_EDITED);
Gary Mai250fc162016-06-10 16:54:15 -0700982 }
Brian Attwelld28851f2014-06-10 13:25:07 -0700983
984 // Check to see whether it comes from the old version.
985 if (lookupUri != null && LEGACY_AUTHORITY.equals(lookupUri.getAuthority())) {
986 final long rawContactId = ContentUris.parseId(lookupUri);
987 lookupUri = RawContacts.getContactLookupUri(getContentResolver(),
988 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
989 }
Brian Attwell05287bf2015-02-25 22:24:04 -0800990 mExtraMode = getIntent().getIntExtra(QuickContact.EXTRA_MODE, QuickContact.MODE_LARGE);
Wenyi Wangcaf26192016-05-09 15:00:25 -0700991 if (isMultiWindowOnPhone()) {
992 mExtraMode = QuickContact.MODE_LARGE;
993 }
994 mExtraPrioritizedMimeType =
995 getIntent().getStringExtra(QuickContact.EXTRA_PRIORITIZED_MIMETYPE);
Brian Attwelld28851f2014-06-10 13:25:07 -0700996 final Uri oldLookupUri = mLookupUri;
997
Marcus Hagerottd105c1e2016-09-30 14:28:00 -0700998
Jay Shraunerfcfcae32014-11-24 10:23:36 -0800999 if (lookupUri == null) {
1000 finish();
1001 return;
1002 }
1003 mLookupUri = lookupUri;
Brian Attwelld28851f2014-06-10 13:25:07 -07001004 mExcludeMimes = intent.getStringArrayExtra(QuickContact.EXTRA_EXCLUDE_MIMES);
1005 if (oldLookupUri == null) {
yaolu79525d02016-08-24 12:08:39 -07001006 // Should not log if only orientation changes.
1007 mShouldLog = !mIsRecreatedInstance;
Brian Attwelld28851f2014-06-10 13:25:07 -07001008 mContactLoader = (ContactLoader) getLoaderManager().initLoader(
1009 LOADER_CONTACT_ID, null, mLoaderContactCallbacks);
1010 } else if (oldLookupUri != mLookupUri) {
yaolu79525d02016-08-24 12:08:39 -07001011 // Should log when reload happens, regardless of orientation change.
1012 mShouldLog = true;
Brian Attwelld28851f2014-06-10 13:25:07 -07001013 // After copying a directory contact, the contact URI changes. Therefore,
Wenyi Wang144a4d12016-02-17 11:46:23 -08001014 // we need to reload the new contact.
Brian Attwell59a585b2014-10-17 18:07:10 -07001015 destroyInteractionLoaders();
Wenyi Wang144a4d12016-02-17 11:46:23 -08001016 mContactLoader = (ContactLoader) (Loader<?>) getLoaderManager().getLoader(
1017 LOADER_CONTACT_ID);
yaolu48610312016-11-21 12:33:35 -08001018 mContactLoader.setNewLookup(mLookupUri);
Brian Attwell59a585b2014-10-17 18:07:10 -07001019 mCachedCp2DataCardModel = null;
Brian Attwelld28851f2014-06-10 13:25:07 -07001020 }
Wenyi Wang144a4d12016-02-17 11:46:23 -08001021 mContactLoader.forceLoad();
Brian Attwell2d150da2014-07-09 22:35:56 -07001022
1023 NfcHandler.register(this, mLookupUri);
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001024 }
1025
Brian Attwell59a585b2014-10-17 18:07:10 -07001026 private void destroyInteractionLoaders() {
1027 for (int interactionLoaderId : mRecentLoaderIds) {
1028 getLoaderManager().destroyLoader(interactionLoaderId);
1029 }
1030 }
1031
Brian Attwellb7e43642014-06-02 14:33:04 -07001032 private void runEntranceAnimation() {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001033 if (mHasAlreadyBeenOpened) {
1034 return;
1035 }
1036 mHasAlreadyBeenOpened = true;
Wenyi Wangcaf26192016-05-09 15:00:25 -07001037 mScroller.scrollUpForEntranceAnimation(/* scrollToCurrentPosition */ !isMultiWindowOnPhone()
1038 && (mExtraMode != MODE_FULLY_EXPANDED));
1039 }
1040
1041 private boolean isMultiWindowOnPhone() {
1042 return MultiWindowCompat.isInMultiWindowMode(this) && PhoneCapabilityTester.isPhone(this);
Brian Attwellb7e43642014-06-02 14:33:04 -07001043 }
1044
Katherine Kuan81281ee2011-07-28 16:20:59 -07001045 /** Assign this string to the view if it is not empty. */
Brian Attwelld28851f2014-06-10 13:25:07 -07001046 private void setHeaderNameText(int resId) {
Brian Attwell9b889e62014-06-23 18:25:32 -07001047 if (mScroller != null) {
Tingting Wang9f153a02015-11-05 18:11:04 -08001048 mScroller.setTitle(getText(resId) == null ? null : getText(resId).toString(),
1049 /* isPhoneNumber= */ false);
Brian Attwell9b889e62014-06-23 18:25:32 -07001050 }
Katherine Kuan81281ee2011-07-28 16:20:59 -07001051 }
1052
1053 /** Assign this string to the view if it is not empty. */
Tingting Wang9f153a02015-11-05 18:11:04 -08001054 private void setHeaderNameText(String value, boolean isPhoneNumber) {
Brian Attwelld28851f2014-06-10 13:25:07 -07001055 if (!TextUtils.isEmpty(value)) {
Brian Attwell9b889e62014-06-23 18:25:32 -07001056 if (mScroller != null) {
Tingting Wang9f153a02015-11-05 18:11:04 -08001057 mScroller.setTitle(value, isPhoneNumber);
Brian Attwell9b889e62014-06-23 18:25:32 -07001058 }
Katherine Kuan81281ee2011-07-28 16:20:59 -07001059 }
1060 }
1061
1062 /**
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001063 * Check if the given MIME-type appears in the list of excluded MIME-types
1064 * that the most-recent caller requested.
1065 */
1066 private boolean isMimeExcluded(String mimeType) {
1067 if (mExcludeMimes == null) return false;
1068 for (String excludedMime : mExcludeMimes) {
1069 if (TextUtils.equals(excludedMime, mimeType)) {
1070 return true;
1071 }
1072 }
1073 return false;
1074 }
1075
1076 /**
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02001077 * Handle the result from the ContactLoader
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001078 */
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001079 private void bindContactData(final Contact data) {
1080 Trace.beginSection("bindContactData");
yaolu79525d02016-08-24 12:08:39 -07001081
1082 final int actionType = mContactData == null ? ActionType.START : ActionType.UNKNOWN_ACTION;
Yorke Lee6cb6f6f2013-09-26 16:26:03 -07001083 mContactData = data;
yaolu79525d02016-08-24 12:08:39 -07001084
1085 final int newContactType;
1086 if (DirectoryContactUtil.isDirectoryContact(mContactData)) {
1087 newContactType = ContactType.DIRECTORY;
1088 } else if (InvisibleContactUtil.isInvisibleAndAddable(mContactData, this)) {
1089 newContactType = ContactType.INVISIBLE_AND_ADDABLE;
1090 } else if (isContactEditable()) {
1091 newContactType = ContactType.EDITABLE;
1092 } else {
1093 newContactType = ContactType.UNKNOWN_TYPE;
1094 }
1095 if (mShouldLog && mContactType != newContactType) {
yaolu7a09c702016-09-01 18:12:03 -07001096 Logger.logQuickContactEvent(mReferrer, newContactType, CardType.UNKNOWN_CARD,
yaolu79525d02016-08-24 12:08:39 -07001097 actionType, /* thirdPartyAction */ null);
1098 }
1099 mContactType = newContactType;
1100
yaolu58a1ac22016-10-24 16:50:24 -07001101 setStateForPhoneMenuItems(mContactData);
Brian Attwelld28851f2014-06-10 13:25:07 -07001102 invalidateOptionsMenu();
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001103
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001104 Trace.endSection();
1105 Trace.beginSection("Set display photo & name");
Makoto Onukidfe8cc82012-05-17 18:03:44 -07001106
Brian Attwell02eaf112014-08-27 21:03:42 -07001107 mPhotoView.setIsBusiness(mContactData.isDisplayNameFromOrganization());
Yorke Lee62196682013-09-06 18:34:17 -07001108 mPhotoSetter.setupContactPhoto(data, mPhotoView);
Brian Attwell31b2d422014-06-05 00:14:58 -07001109 extractAndApplyTintFromPhotoViewAsynchronously();
Tingting Wangdf65d162015-07-24 17:04:35 -07001110 final String displayName = ContactDisplayUtils.getDisplayName(this, data).toString();
Tingting Wang9f153a02015-11-05 18:11:04 -08001111 setHeaderNameText(
1112 displayName, mContactData.getDisplayNameSource() == DisplayNameSources.PHONE);
Tingting Wangdf65d162015-07-24 17:04:35 -07001113 final String phoneticName = ContactDisplayUtils.getPhoneticName(this, data);
1114 if (mScroller != null) {
Tingting Wang469a70f2016-03-08 14:50:29 -08001115 // Show phonetic name only when it doesn't equal the display name.
1116 if (!TextUtils.isEmpty(phoneticName) && !phoneticName.equals(displayName)) {
Tingting Wangdf65d162015-07-24 17:04:35 -07001117 mScroller.setPhoneticName(phoneticName);
1118 } else {
1119 mScroller.setPhoneticNameGone();
1120 }
Tingting Wangd5b686e2015-07-13 12:52:40 -07001121 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001122
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001123 Trace.endSection();
Makoto Onukidfe8cc82012-05-17 18:03:44 -07001124
Brian Attwell6bb01342014-08-20 23:16:00 -07001125 mEntriesAndActionsTask = new AsyncTask<Void, Void, Cp2DataCardModel>() {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001126
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001127 @Override
Brian Attwell6bb01342014-08-20 23:16:00 -07001128 protected Cp2DataCardModel doInBackground(
Paul Souloseb64a4b2014-07-07 17:03:27 -07001129 Void... params) {
1130 return generateDataModelFromContact(data);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001131 }
1132
1133 @Override
Brian Attwell6bb01342014-08-20 23:16:00 -07001134 protected void onPostExecute(Cp2DataCardModel cardDataModel) {
1135 super.onPostExecute(cardDataModel);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001136 // Check that original AsyncTask parameters are still valid and the activity
1137 // is still running before binding to UI. A new intent could invalidate
1138 // the results, for example.
1139 if (data == mContactData && !isCancelled()) {
Brian Attwell6bb01342014-08-20 23:16:00 -07001140 bindDataToCards(cardDataModel);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001141 showActivity();
1142 }
1143 }
1144 };
1145 mEntriesAndActionsTask.execute();
1146 }
1147
Brian Attwell6bb01342014-08-20 23:16:00 -07001148 private void bindDataToCards(Cp2DataCardModel cp2DataCardModel) {
1149 startInteractionLoaders(cp2DataCardModel);
Wenyi Wanga26a57a2016-03-02 17:53:23 -08001150 populateContactAndAboutCard(cp2DataCardModel, /* shouldAddPhoneticName */ true);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001151 }
1152
Brian Attwell6bb01342014-08-20 23:16:00 -07001153 private void startInteractionLoaders(Cp2DataCardModel cp2DataCardModel) {
1154 final Map<String, List<DataItem>> dataItemsMap = cp2DataCardModel.dataItemsMap;
1155 final List<DataItem> phoneDataItems = dataItemsMap.get(Phone.CONTENT_ITEM_TYPE);
guanxiongliuc7a4b9c2016-04-30 20:19:21 -07001156 final List<DataItem> sipCallDataItems = dataItemsMap.get(SipAddress.CONTENT_ITEM_TYPE);
Paul Soulos97f27802014-09-08 13:55:45 -07001157 if (phoneDataItems != null && phoneDataItems.size() == 1) {
1158 mOnlyOnePhoneNumber = true;
1159 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001160 String[] phoneNumbers = null;
1161 if (phoneDataItems != null) {
1162 phoneNumbers = new String[phoneDataItems.size()];
1163 for (int i = 0; i < phoneDataItems.size(); ++i) {
1164 phoneNumbers[i] = ((PhoneDataItem) phoneDataItems.get(i)).getNumber();
1165 }
1166 }
guanxiongliuc7a4b9c2016-04-30 20:19:21 -07001167 String[] sipNumbers = null;
1168 if (sipCallDataItems != null) {
1169 sipNumbers = new String[sipCallDataItems.size()];
1170 for (int i = 0; i < sipCallDataItems.size(); ++i) {
1171 sipNumbers[i] = ((SipAddressDataItem) sipCallDataItems.get(i)).getSipAddress();
1172 }
1173 }
Paul Soulosab840442014-06-17 14:08:40 -07001174 final Bundle phonesExtraBundle = new Bundle();
Paul Souloseb64a4b2014-07-07 17:03:27 -07001175 phonesExtraBundle.putStringArray(KEY_LOADER_EXTRA_PHONES, phoneNumbers);
guanxiongliuc7a4b9c2016-04-30 20:19:21 -07001176 phonesExtraBundle.putStringArray(KEY_LOADER_EXTRA_SIP_NUMBERS, sipNumbers);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001177
1178 Trace.beginSection("start sms loader");
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001179 getLoaderManager().initLoader(
1180 LOADER_SMS_ID,
Paul Soulosab840442014-06-17 14:08:40 -07001181 phonesExtraBundle,
1182 mLoaderInteractionsCallbacks);
1183 Trace.endSection();
1184
1185 Trace.beginSection("start call log loader");
1186 getLoaderManager().initLoader(
1187 LOADER_CALL_LOG_ID,
1188 phonesExtraBundle,
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001189 mLoaderInteractionsCallbacks);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001190 Trace.endSection();
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001191
Paul Souloseb64a4b2014-07-07 17:03:27 -07001192
Paul Soulos899aa212014-06-11 12:04:43 -07001193 Trace.beginSection("start calendar loader");
Brian Attwell6bb01342014-08-20 23:16:00 -07001194 final List<DataItem> emailDataItems = dataItemsMap.get(Email.CONTENT_ITEM_TYPE);
Paul Soulos97f27802014-09-08 13:55:45 -07001195 if (emailDataItems != null && emailDataItems.size() == 1) {
1196 mOnlyOneEmail = true;
1197 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001198 String[] emailAddresses = null;
1199 if (emailDataItems != null) {
1200 emailAddresses = new String[emailDataItems.size()];
1201 for (int i = 0; i < emailDataItems.size(); ++i) {
1202 emailAddresses[i] = ((EmailDataItem) emailDataItems.get(i)).getAddress();
1203 }
1204 }
Paul Soulosab840442014-06-17 14:08:40 -07001205 final Bundle emailsExtraBundle = new Bundle();
Paul Souloseb64a4b2014-07-07 17:03:27 -07001206 emailsExtraBundle.putStringArray(KEY_LOADER_EXTRA_EMAILS, emailAddresses);
Paul Soulos899aa212014-06-11 12:04:43 -07001207 getLoaderManager().initLoader(
1208 LOADER_CALENDAR_ID,
Paul Soulosab840442014-06-17 14:08:40 -07001209 emailsExtraBundle,
Paul Soulos899aa212014-06-11 12:04:43 -07001210 mLoaderInteractionsCallbacks);
1211 Trace.endSection();
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001212 }
1213
1214 private void showActivity() {
1215 if (mScroller != null) {
1216 mScroller.setVisibility(View.VISIBLE);
1217 SchedulingUtils.doOnPreDraw(mScroller, /* drawNextFrame = */ false,
1218 new Runnable() {
1219 @Override
1220 public void run() {
1221 runEntranceAnimation();
1222 }
1223 });
1224 }
1225 }
1226
Brian Attwell6bb01342014-08-20 23:16:00 -07001227 private List<List<Entry>> buildAboutCardEntries(Map<String, List<DataItem>> dataItemsMap) {
Paul Soulosa153dba2014-08-06 13:44:57 -07001228 final List<List<Entry>> aboutCardEntries = new ArrayList<>();
1229 for (String mimetype : SORTED_ABOUT_CARD_MIMETYPES) {
Brian Attwell6bb01342014-08-20 23:16:00 -07001230 final List<DataItem> mimeTypeItems = dataItemsMap.get(mimetype);
Paul Soulosa153dba2014-08-06 13:44:57 -07001231 if (mimeTypeItems == null) {
1232 continue;
1233 }
Brian Attwell6bb01342014-08-20 23:16:00 -07001234 // Set aboutCardTitleOut = null, since SORTED_ABOUT_CARD_MIMETYPES doesn't contain
1235 // the name mimetype.
1236 final List<Entry> aboutEntries = dataItemsToEntries(mimeTypeItems,
1237 /* aboutCardTitleOut = */ null);
Paul Soulosa153dba2014-08-06 13:44:57 -07001238 if (aboutEntries.size() > 0) {
1239 aboutCardEntries.add(aboutEntries);
1240 }
1241 }
1242 return aboutCardEntries;
1243 }
1244
Paul Soulosa42ef762014-08-20 10:26:10 -07001245 @Override
1246 protected void onResume() {
1247 super.onResume();
1248 // If returning from a launched activity, repopulate the contact and about card
1249 if (mHasIntentLaunched) {
1250 mHasIntentLaunched = false;
Wenyi Wanga26a57a2016-03-02 17:53:23 -08001251 populateContactAndAboutCard(mCachedCp2DataCardModel, /* shouldAddPhoneticName */ false);
Paul Soulosa42ef762014-08-20 10:26:10 -07001252 }
Brian Attwell59a585b2014-10-17 18:07:10 -07001253
1254 // When exiting the activity and resuming, we want to force a full reload of all the
1255 // interaction data in case something changed in the background. On screen rotation,
1256 // we don't need to do this. And, mCachedCp2DataCardModel will be null, so we won't.
1257 if (mCachedCp2DataCardModel != null) {
1258 destroyInteractionLoaders();
1259 startInteractionLoaders(mCachedCp2DataCardModel);
1260 }
Gary Maib9065dd2016-11-08 10:49:00 -08001261 maybeShowProgressDialog();
1262 }
1263
1264
1265 @Override
1266 protected void onPause() {
1267 super.onPause();
1268 dismissProgressBar();
Paul Soulosa42ef762014-08-20 10:26:10 -07001269 }
1270
Wenyi Wanga26a57a2016-03-02 17:53:23 -08001271 private void populateContactAndAboutCard(Cp2DataCardModel cp2DataCardModel,
1272 boolean shouldAddPhoneticName) {
Brian Attwell6bb01342014-08-20 23:16:00 -07001273 mCachedCp2DataCardModel = cp2DataCardModel;
1274 if (mHasIntentLaunched || cp2DataCardModel == null) {
Paul Soulosa42ef762014-08-20 10:26:10 -07001275 return;
1276 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001277 Trace.beginSection("bind contact card");
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001278
Brian Attwell6bb01342014-08-20 23:16:00 -07001279 final List<List<Entry>> contactCardEntries = cp2DataCardModel.contactCardEntries;
1280 final List<List<Entry>> aboutCardEntries = cp2DataCardModel.aboutCardEntries;
1281 final String customAboutCardName = cp2DataCardModel.customAboutCardName;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001282
1283 if (contactCardEntries.size() > 0) {
1284 mContactCard.initialize(contactCardEntries,
1285 /* numInitialVisibleEntries = */ MIN_NUM_CONTACT_ENTRIES_SHOWN,
Paul Soulosc8e2a912014-07-21 14:52:20 -07001286 /* isExpanded = */ mContactCard.isExpanded(),
yaolu139a03b2016-09-02 17:44:10 -07001287 /* isAlwaysExpanded = */ true,
Paul Soulos0cda9ae2014-07-23 11:27:28 -07001288 mExpandingEntryCardViewListener,
yaolu139a03b2016-09-02 17:44:10 -07001289 mScroller);
yaolu79525d02016-08-24 12:08:39 -07001290 if (mContactCard.getVisibility() == View.GONE && mShouldLog) {
1291 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.CONTACT,
1292 ActionType.UNKNOWN_ACTION, /* thirdPartyAction */ null);
1293 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001294 mContactCard.setVisibility(View.VISIBLE);
1295 } else {
1296 mContactCard.setVisibility(View.GONE);
1297 }
1298 Trace.endSection();
1299
1300 Trace.beginSection("bind about card");
Paul Soulosc6e11092014-07-22 09:22:27 -07001301 // Phonetic name is not a data item, so the entry needs to be created separately
Wenyi Wanga26a57a2016-03-02 17:53:23 -08001302 // But if mCachedCp2DataCardModel is passed to this method (e.g. returning from editor
1303 // without saving any changes), then it should include phoneticName and the phoneticName
1304 // shouldn't be changed. If this is the case, we shouldn't add it again. b/27459294
Paul Soulosc6e11092014-07-22 09:22:27 -07001305 final String phoneticName = mContactData.getPhoneticName();
Wenyi Wanga26a57a2016-03-02 17:53:23 -08001306 if (shouldAddPhoneticName && !TextUtils.isEmpty(phoneticName)) {
Paul Soulosc6e11092014-07-22 09:22:27 -07001307 Entry phoneticEntry = new Entry(/* viewId = */ -1,
1308 /* icon = */ null,
1309 getResources().getString(R.string.name_phonetic),
1310 phoneticName,
Walter Jang8a3fe522014-10-16 11:12:13 -07001311 /* subHeaderIcon = */ null,
Paul Soulosc6e11092014-07-22 09:22:27 -07001312 /* text = */ null,
Walter Jang8a3fe522014-10-16 11:12:13 -07001313 /* textIcon = */ null,
Paul Soulos23e28362014-08-29 14:57:08 -07001314 /* primaryContentDescription = */ null,
Paul Soulosc6e11092014-07-22 09:22:27 -07001315 /* intent = */ null,
1316 /* alternateIcon = */ null,
1317 /* alternateIntent = */ null,
1318 /* alternateContentDescription = */ null,
1319 /* shouldApplyColor = */ false,
Paul Soulos2a4207f2014-07-31 17:09:05 -07001320 /* isEditable = */ false,
1321 /* EntryContextMenuInfo = */ new EntryContextMenuInfo(phoneticName,
Paul Soulos97f27802014-09-08 13:55:45 -07001322 getResources().getString(R.string.name_phonetic),
1323 /* mimeType = */ null, /* id = */ -1, /* isPrimary = */ false),
Paul Soulos48fc9122014-08-26 13:52:36 -07001324 /* thirdIcon = */ null,
1325 /* thirdIntent = */ null,
Paul Soulos48290be2014-09-08 13:44:51 -07001326 /* thirdContentDescription = */ null,
Tyler Gunn5f87e922015-08-05 14:24:52 -07001327 /* thirdAction = */ Entry.ACTION_NONE,
1328 /* thirdExtras = */ null,
1329 /* iconResourceId = */ 0);
Paul Soulosc6e11092014-07-22 09:22:27 -07001330 List<Entry> phoneticList = new ArrayList<>();
1331 phoneticList.add(phoneticEntry);
Paul Soulosa153dba2014-08-06 13:44:57 -07001332 // Phonetic name comes after nickname. Check to see if the first entry type is nickname
Paul Soulosa0fa4c12014-08-07 12:01:32 -07001333 if (aboutCardEntries.size() > 0 && aboutCardEntries.get(0).get(0).getHeader().equals(
Paul Soulosa153dba2014-08-06 13:44:57 -07001334 getResources().getString(R.string.header_nickname_entry))) {
1335 aboutCardEntries.add(1, phoneticList);
1336 } else {
1337 aboutCardEntries.add(0, phoneticList);
1338 }
Paul Soulosc6e11092014-07-22 09:22:27 -07001339 }
1340
Brian Attwell6bb01342014-08-20 23:16:00 -07001341 if (!TextUtils.isEmpty(customAboutCardName)) {
1342 mAboutCard.setTitle(customAboutCardName);
1343 }
1344
kungaox3b1a8b22015-02-05 15:29:19 +08001345 mAboutCard.initialize(aboutCardEntries,
1346 /* numInitialVisibleEntries = */ 1,
1347 /* isExpanded = */ true,
1348 /* isAlwaysExpanded = */ true,
1349 mExpandingEntryCardViewListener,
1350 mScroller);
Brian Attwell60953692014-07-11 17:18:46 -07001351
1352 if (contactCardEntries.size() == 0 && aboutCardEntries.size() == 0) {
yaolu95a13452016-09-20 15:25:25 -07001353 initializeNoContactDetailCard(cp2DataCardModel.areAllRawContactsSimAccounts);
Brian Attwell60953692014-07-11 17:18:46 -07001354 } else {
1355 mNoContactDetailsCard.setVisibility(View.GONE);
1356 }
1357
Paul Soulosa8fce962014-07-15 13:37:24 -07001358 // If the Recent card is already initialized (all recent data is loaded), show the About
1359 // card if it has entries. Otherwise About card visibility will be set in bindRecentData()
yaolu79525d02016-08-24 12:08:39 -07001360 if (aboutCardEntries.size() > 0) {
1361 if (mAboutCard.getVisibility() == View.GONE && mShouldLog) {
1362 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.ABOUT,
1363 ActionType.UNKNOWN_ACTION, /* thirdPartyAction */ null);
1364 }
1365 if (isAllRecentDataLoaded()) {
1366 mAboutCard.setVisibility(View.VISIBLE);
1367 }
Paul Soulosa8fce962014-07-15 13:37:24 -07001368 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001369 Trace.endSection();
1370 }
1371
1372 /**
Brian Attwell60953692014-07-11 17:18:46 -07001373 * Create a card that shows "Add email" and "Add phone number" entries in grey.
yaolu95a13452016-09-20 15:25:25 -07001374 * When contact is a SIM contact, only shows "Add phone number".
Brian Attwell60953692014-07-11 17:18:46 -07001375 */
yaolu95a13452016-09-20 15:25:25 -07001376 private void initializeNoContactDetailCard(boolean areAllRawContactsSimAccounts) {
Brian Attwell60953692014-07-11 17:18:46 -07001377 final Drawable phoneIcon = getResources().getDrawable(
1378 R.drawable.ic_phone_24dp).mutate();
1379 final Entry phonePromptEntry = new Entry(CARD_ENTRY_ID_EDIT_CONTACT,
1380 phoneIcon, getString(R.string.quickcontact_add_phone_number),
Walter Jang8a3fe522014-10-16 11:12:13 -07001381 /* subHeader = */ null, /* subHeaderIcon = */ null, /* text = */ null,
1382 /* textIcon = */ null, /* primaryContentDescription = */ null,
Paul Soulos23e28362014-08-29 14:57:08 -07001383 getEditContactIntent(),
Paul Soulosdd7419d2014-07-15 11:22:13 -07001384 /* alternateIcon = */ null, /* alternateIntent = */ null,
Paul Soulos714455b2014-07-15 14:32:01 -07001385 /* alternateContentDescription = */ null, /* shouldApplyColor = */ true,
Paul Soulos48fc9122014-08-26 13:52:36 -07001386 /* isEditable = */ false, /* EntryContextMenuInfo = */ null,
1387 /* thirdIcon = */ null, /* thirdIntent = */ null,
Tyler Gunn5f87e922015-08-05 14:24:52 -07001388 /* thirdContentDescription = */ null,
1389 /* thirdAction = */ Entry.ACTION_NONE,
1390 /* thirdExtras = */ null,
1391 R.drawable.ic_phone_24dp);
Brian Attwell60953692014-07-11 17:18:46 -07001392
Brian Attwell60953692014-07-11 17:18:46 -07001393 final List<List<Entry>> promptEntries = new ArrayList<>();
1394 promptEntries.add(new ArrayList<Entry>(1));
Brian Attwell60953692014-07-11 17:18:46 -07001395 promptEntries.get(0).add(phonePromptEntry);
yaolu95a13452016-09-20 15:25:25 -07001396
1397 if (!areAllRawContactsSimAccounts) {
1398 final Drawable emailIcon = getResources().getDrawable(
1399 R.drawable.ic_email_24dp).mutate();
1400 final Entry emailPromptEntry = new Entry(CARD_ENTRY_ID_EDIT_CONTACT,
1401 emailIcon, getString(R.string.quickcontact_add_email), /* subHeader = */ null,
1402 /* subHeaderIcon = */ null,
1403 /* text = */ null, /* textIcon = */ null, /* primaryContentDescription = */ null,
1404 getEditContactIntent(), /* alternateIcon = */ null,
1405 /* alternateIntent = */ null, /* alternateContentDescription = */ null,
1406 /* shouldApplyColor = */ true, /* isEditable = */ false,
1407 /* EntryContextMenuInfo = */ null, /* thirdIcon = */ null,
1408 /* thirdIntent = */ null, /* thirdContentDescription = */ null,
1409 /* thirdAction = */ Entry.ACTION_NONE, /* thirdExtras = */ null,
1410 R.drawable.ic_email_24dp);
1411
1412 promptEntries.add(new ArrayList<Entry>(1));
1413 promptEntries.get(1).add(emailPromptEntry);
1414 }
Brian Attwell60953692014-07-11 17:18:46 -07001415
1416 final int subHeaderTextColor = getResources().getColor(
1417 R.color.quickcontact_entry_sub_header_text_color);
1418 final PorterDuffColorFilter greyColorFilter =
1419 new PorterDuffColorFilter(subHeaderTextColor, PorterDuff.Mode.SRC_ATOP);
Paul Soulosc8e2a912014-07-21 14:52:20 -07001420 mNoContactDetailsCard.initialize(promptEntries, 2, /* isExpanded = */ true,
Paul Soulos0cda9ae2014-07-23 11:27:28 -07001421 /* isAlwaysExpanded = */ true, mExpandingEntryCardViewListener, mScroller);
yaolu79525d02016-08-24 12:08:39 -07001422 if (mNoContactDetailsCard.getVisibility() == View.GONE && mShouldLog) {
1423 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.NO_CONTACT,
1424 ActionType.UNKNOWN_ACTION, /* thirdPartyAction */ null);
1425 }
Brian Attwell60953692014-07-11 17:18:46 -07001426 mNoContactDetailsCard.setVisibility(View.VISIBLE);
1427 mNoContactDetailsCard.setEntryHeaderColor(subHeaderTextColor);
1428 mNoContactDetailsCard.setColorAndFilter(subHeaderTextColor, greyColorFilter);
1429 }
1430
1431 /**
Paul Souloseb64a4b2014-07-07 17:03:27 -07001432 * Builds the {@link DataItem}s Map out of the Contact.
1433 * @param data The contact to build the data from.
1434 * @return A pair containing a list of data items sorted within mimetype and sorted
1435 * amongst mimetype. The map goes from mimetype string to the sorted list of data items within
1436 * mimetype
1437 */
Brian Attwell6bb01342014-08-20 23:16:00 -07001438 private Cp2DataCardModel generateDataModelFromContact(
Paul Souloseb64a4b2014-07-07 17:03:27 -07001439 Contact data) {
1440 Trace.beginSection("Build data items map");
1441
1442 final Map<String, List<DataItem>> dataItemsMap = new HashMap<>();
Brian Attwell8bf96e72014-06-25 15:46:47 -07001443
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001444 final ResolveCache cache = ResolveCache.getInstance(this);
Maurice Chu851222a2012-06-21 11:43:08 -07001445 for (RawContact rawContact : data.getRawContacts()) {
1446 for (DataItem dataItem : rawContact.getDataItems()) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07001447 dataItem.setRawContactId(rawContact.getId());
1448
Maurice Chu851222a2012-06-21 11:43:08 -07001449 final String mimeType = dataItem.getMimeType();
Paul Souloseb64a4b2014-07-07 17:03:27 -07001450 if (mimeType == null) continue;
1451
Chiao Cheng47b6f702012-09-07 17:28:17 -07001452 final AccountType accountType = rawContact.getAccountType(this);
1453 final DataKind dataKind = AccountTypeManager.getInstance(this)
1454 .getKindOrFallback(accountType, mimeType);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001455 if (dataKind == null) continue;
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001456
Paul Souloseb64a4b2014-07-07 17:03:27 -07001457 dataItem.setDataKind(dataKind);
1458
1459 final boolean hasData = !TextUtils.isEmpty(dataItem.buildDataString(this,
1460 dataKind));
1461
1462 if (isMimeExcluded(mimeType) || !hasData) continue;
1463
1464 List<DataItem> dataItemListByType = dataItemsMap.get(mimeType);
1465 if (dataItemListByType == null) {
1466 dataItemListByType = new ArrayList<>();
1467 dataItemsMap.put(mimeType, dataItemListByType);
Paul Soulosb3054e52014-06-05 16:46:02 -07001468 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001469 dataItemListByType.add(dataItem);
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001470 }
1471 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001472 Trace.endSection();
Makoto Onukidfe8cc82012-05-17 18:03:44 -07001473
Paul Souloseb64a4b2014-07-07 17:03:27 -07001474 Trace.beginSection("sort within mimetypes");
Paul Soulos16339332014-06-24 13:59:56 -07001475 /*
1476 * 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 -07001477 * used data items, one per mimetype. Then, within each mimetype, the list of data items
1478 * for that type is also sorted, based off of {super primary, primary, times used} in that
1479 * order.
Paul Soulos16339332014-06-24 13:59:56 -07001480 */
Paul Souloseb64a4b2014-07-07 17:03:27 -07001481 final List<List<DataItem>> dataItemsList = new ArrayList<>();
1482 for (List<DataItem> mimeTypeDataItems : dataItemsMap.values()) {
1483 // Remove duplicate data items
1484 Collapser.collapseList(mimeTypeDataItems, this);
1485 // Sort within mimetype
1486 Collections.sort(mimeTypeDataItems, mWithinMimeTypeDataItemComparator);
1487 // Add to the list of data item lists
1488 dataItemsList.add(mimeTypeDataItems);
1489 }
1490 Trace.endSection();
Paul Soulos16339332014-06-24 13:59:56 -07001491
Paul Souloseb64a4b2014-07-07 17:03:27 -07001492 Trace.beginSection("sort amongst mimetypes");
1493 // Sort amongst mimetypes to bubble up the top data items for the contact card
1494 Collections.sort(dataItemsList, mAmongstMimeTypeDataItemComparator);
1495 Trace.endSection();
1496
Brian Attwell6bb01342014-08-20 23:16:00 -07001497 Trace.beginSection("cp2 data items to entries");
1498
1499 final List<List<Entry>> contactCardEntries = new ArrayList<>();
1500 final List<List<Entry>> aboutCardEntries = buildAboutCardEntries(dataItemsMap);
1501 final MutableString aboutCardName = new MutableString();
1502
1503 for (int i = 0; i < dataItemsList.size(); ++i) {
1504 final List<DataItem> dataItemsByMimeType = dataItemsList.get(i);
1505 final DataItem topDataItem = dataItemsByMimeType.get(0);
1506 if (SORTED_ABOUT_CARD_MIMETYPES.contains(topDataItem.getMimeType())) {
1507 // About card mimetypes are built in buildAboutCardEntries, skip here
1508 continue;
1509 } else {
1510 List<Entry> contactEntries = dataItemsToEntries(dataItemsList.get(i),
1511 aboutCardName);
1512 if (contactEntries.size() > 0) {
1513 contactCardEntries.add(contactEntries);
1514 }
1515 }
1516 }
1517
1518 Trace.endSection();
1519
1520 final Cp2DataCardModel dataModel = new Cp2DataCardModel();
1521 dataModel.customAboutCardName = aboutCardName.value;
1522 dataModel.aboutCardEntries = aboutCardEntries;
1523 dataModel.contactCardEntries = contactCardEntries;
1524 dataModel.dataItemsMap = dataItemsMap;
yaolu95a13452016-09-20 15:25:25 -07001525 dataModel.areAllRawContactsSimAccounts = data.areAllRawContactsSimAccounts(this);
Brian Attwell6bb01342014-08-20 23:16:00 -07001526 return dataModel;
1527 }
1528
1529 /**
1530 * Class used to hold the About card and Contact cards' data model that gets generated
1531 * on a background thread. All data is from CP2.
1532 */
1533 private static class Cp2DataCardModel {
1534 /**
1535 * A map between a mimetype string and the corresponding list of data items. The data items
1536 * are in sorted order using mWithinMimeTypeDataItemComparator.
1537 */
1538 public Map<String, List<DataItem>> dataItemsMap;
1539 public List<List<Entry>> aboutCardEntries;
1540 public List<List<Entry>> contactCardEntries;
1541 public String customAboutCardName;
yaolu95a13452016-09-20 15:25:25 -07001542 public boolean areAllRawContactsSimAccounts;
Brian Attwell6bb01342014-08-20 23:16:00 -07001543 }
1544
1545 private static class MutableString {
1546 public String value;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001547 }
1548
1549 /**
1550 * Converts a {@link DataItem} into an {@link ExpandingEntryCardView.Entry} for display.
1551 * If the {@link ExpandingEntryCardView.Entry} has no visual elements, null is returned.
Brian Attwell6bb01342014-08-20 23:16:00 -07001552 *
1553 * This runs on a background thread. This is set as static to avoid accidentally adding
1554 * additional dependencies on unsafe things (like the Activity).
1555 *
Paul Souloseb64a4b2014-07-07 17:03:27 -07001556 * @param dataItem The {@link DataItem} to convert.
Paul Soulos6a2a1a72014-10-10 15:52:56 -07001557 * @param secondDataItem A second {@link DataItem} to help build a full entry for some
1558 * mimetypes
Paul Souloseb64a4b2014-07-07 17:03:27 -07001559 * @return The {@link ExpandingEntryCardView.Entry}, or null if no visual elements are present.
1560 */
Paul Soulos6a2a1a72014-10-10 15:52:56 -07001561 private static Entry dataItemToEntry(DataItem dataItem, DataItem secondDataItem,
Brian Attwell6bb01342014-08-20 23:16:00 -07001562 Context context, Contact contactData,
1563 final MutableString aboutCardName) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07001564 Drawable icon = null;
1565 String header = null;
1566 String subHeader = null;
1567 Drawable subHeaderIcon = null;
1568 String text = null;
1569 Drawable textIcon = null;
Paul Soulos23e28362014-08-29 14:57:08 -07001570 StringBuilder primaryContentDescription = new StringBuilder();
Tingting Wangb6949dc2015-11-03 13:17:12 -08001571 Spannable phoneContentDescription = null;
Wenyi Wang5da55ff2015-11-19 13:22:40 -08001572 Spannable smsContentDescription = null;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001573 Intent intent = null;
Paul Soulos48ebbaa2014-07-15 13:11:23 -07001574 boolean shouldApplyColor = true;
Paul Soulosdd7419d2014-07-15 11:22:13 -07001575 Drawable alternateIcon = null;
1576 Intent alternateIntent = null;
Paul Soulos23e28362014-08-29 14:57:08 -07001577 StringBuilder alternateContentDescription = new StringBuilder();
Paul Souloseb64a4b2014-07-07 17:03:27 -07001578 final boolean isEditable = false;
Paul Soulos2a4207f2014-07-31 17:09:05 -07001579 EntryContextMenuInfo entryContextMenuInfo = null;
Paul Soulos48fc9122014-08-26 13:52:36 -07001580 Drawable thirdIcon = null;
1581 Intent thirdIntent = null;
Tyler Gunn5f87e922015-08-05 14:24:52 -07001582 int thirdAction = Entry.ACTION_NONE;
Paul Soulos48fc9122014-08-26 13:52:36 -07001583 String thirdContentDescription = null;
Tyler Gunn5f87e922015-08-05 14:24:52 -07001584 Bundle thirdExtras = null;
Paul Soulos48290be2014-09-08 13:44:51 -07001585 int iconResourceId = 0;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001586
Brian Attwell6bb01342014-08-20 23:16:00 -07001587 context = context.getApplicationContext();
Paul Soulos23e28362014-08-29 14:57:08 -07001588 final Resources res = context.getResources();
Paul Souloseb64a4b2014-07-07 17:03:27 -07001589 DataKind kind = dataItem.getDataKind();
1590
1591 if (dataItem instanceof ImDataItem) {
1592 final ImDataItem im = (ImDataItem) dataItem;
Brian Attwell6bb01342014-08-20 23:16:00 -07001593 intent = ContactsUtils.buildImIntent(context, im).first;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001594 final boolean isEmail = im.isCreatedFromEmail();
Paul Soulos7de6f852014-07-28 14:07:28 -07001595 final int protocol;
1596 if (!im.isProtocolValid()) {
1597 protocol = Im.PROTOCOL_CUSTOM;
1598 } else {
1599 protocol = isEmail ? Im.PROTOCOL_GOOGLE_TALK : im.getProtocol();
1600 }
1601 if (protocol == Im.PROTOCOL_CUSTOM) {
1602 // If the protocol is custom, display the "IM" entry header as well to distinguish
1603 // this entry from other ones
Paul Soulos23e28362014-08-29 14:57:08 -07001604 header = res.getString(R.string.header_im_entry);
1605 subHeader = Im.getProtocolLabel(res, protocol,
Paul Soulos7de6f852014-07-28 14:07:28 -07001606 im.getCustomProtocol()).toString();
1607 text = im.getData();
1608 } else {
Paul Soulos23e28362014-08-29 14:57:08 -07001609 header = Im.getProtocolLabel(res, protocol,
Paul Soulos7de6f852014-07-28 14:07:28 -07001610 im.getCustomProtocol()).toString();
1611 subHeader = im.getData();
1612 }
Paul Soulos97f27802014-09-08 13:55:45 -07001613 entryContextMenuInfo = new EntryContextMenuInfo(im.getData(), header,
1614 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001615 } else if (dataItem instanceof OrganizationDataItem) {
1616 final OrganizationDataItem organization = (OrganizationDataItem) dataItem;
Paul Soulos23e28362014-08-29 14:57:08 -07001617 header = res.getString(R.string.header_organization_entry);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001618 subHeader = organization.getCompany();
Paul Soulos97f27802014-09-08 13:55:45 -07001619 entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header,
1620 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001621 text = organization.getTitle();
1622 } else if (dataItem instanceof NicknameDataItem) {
1623 final NicknameDataItem nickname = (NicknameDataItem) dataItem;
1624 // Build nickname entries
1625 final boolean isNameRawContact =
Brian Attwell6bb01342014-08-20 23:16:00 -07001626 (contactData.getNameRawContactId() == dataItem.getRawContactId());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001627
1628 final boolean duplicatesTitle =
1629 isNameRawContact
Brian Attwell6bb01342014-08-20 23:16:00 -07001630 && contactData.getDisplayNameSource() == DisplayNameSources.NICKNAME;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001631
1632 if (!duplicatesTitle) {
Paul Soulos23e28362014-08-29 14:57:08 -07001633 header = res.getString(R.string.header_nickname_entry);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001634 subHeader = nickname.getName();
Paul Soulos97f27802014-09-08 13:55:45 -07001635 entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header,
1636 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001637 }
yaolu1bd88262016-08-18 10:29:12 -07001638 } else if (dataItem instanceof CustomDataItem) {
1639 final CustomDataItem customDataItem = (CustomDataItem) dataItem;
1640 final String summary = customDataItem.getSummary();
1641 header = TextUtils.isEmpty(summary)
1642 ? res.getString(R.string.label_custom_field) : summary;
1643 subHeader = customDataItem.getContent();
1644 entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header,
1645 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001646 } else if (dataItem instanceof NoteDataItem) {
1647 final NoteDataItem note = (NoteDataItem) dataItem;
Paul Soulos23e28362014-08-29 14:57:08 -07001648 header = res.getString(R.string.header_note_entry);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001649 subHeader = note.getNote();
Paul Soulos97f27802014-09-08 13:55:45 -07001650 entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header,
1651 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001652 } else if (dataItem instanceof WebsiteDataItem) {
1653 final WebsiteDataItem website = (WebsiteDataItem) dataItem;
Paul Soulos23e28362014-08-29 14:57:08 -07001654 header = res.getString(R.string.header_website_entry);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001655 subHeader = website.getUrl();
Paul Soulos97f27802014-09-08 13:55:45 -07001656 entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header,
1657 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001658 try {
Brian Attwell3bcf09e2014-12-08 16:16:05 -08001659 final WebAddress webAddress = new WebAddress(website.buildDataStringForDisplay
1660 (context, kind));
Paul Souloseb64a4b2014-07-07 17:03:27 -07001661 intent = new Intent(Intent.ACTION_VIEW, Uri.parse(webAddress.toString()));
1662 } catch (final ParseException e) {
Brian Attwell3bcf09e2014-12-08 16:16:05 -08001663 Log.e(TAG, "Couldn't parse website: " + website.buildDataStringForDisplay(
1664 context, kind));
Paul Souloseb64a4b2014-07-07 17:03:27 -07001665 }
1666 } else if (dataItem instanceof EventDataItem) {
1667 final EventDataItem event = (EventDataItem) dataItem;
Brian Attwell3bcf09e2014-12-08 16:16:05 -08001668 final String dataString = event.buildDataStringForDisplay(context, kind);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001669 final Calendar cal = DateUtils.parseDate(dataString, false);
1670 if (cal != null) {
1671 final Date nextAnniversary =
1672 DateUtils.getNextAnnualDate(cal);
1673 final Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
1674 builder.appendPath("time");
1675 ContentUris.appendId(builder, nextAnniversary.getTime());
1676 intent = new Intent(Intent.ACTION_VIEW).setData(builder.build());
1677 }
Paul Soulos23e28362014-08-29 14:57:08 -07001678 header = res.getString(R.string.header_event_entry);
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001679 if (event.hasKindTypeColumn(kind)) {
Wenyi Wangc85af282015-12-21 15:55:32 -08001680 subHeader = EventCompat.getTypeLabel(res, event.getKindTypeColumn(kind),
Paul Soulos7de6f852014-07-28 14:07:28 -07001681 event.getLabel()).toString();
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001682 }
Brian Attwell6bb01342014-08-20 23:16:00 -07001683 text = DateUtils.formatDate(context, dataString);
Paul Soulos97f27802014-09-08 13:55:45 -07001684 entryContextMenuInfo = new EntryContextMenuInfo(text, header,
1685 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001686 } else if (dataItem instanceof RelationDataItem) {
1687 final RelationDataItem relation = (RelationDataItem) dataItem;
Brian Attwell3bcf09e2014-12-08 16:16:05 -08001688 final String dataString = relation.buildDataStringForDisplay(context, kind);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001689 if (!TextUtils.isEmpty(dataString)) {
1690 intent = new Intent(Intent.ACTION_SEARCH);
1691 intent.putExtra(SearchManager.QUERY, dataString);
1692 intent.setType(Contacts.CONTENT_TYPE);
1693 }
Paul Soulos23e28362014-08-29 14:57:08 -07001694 header = res.getString(R.string.header_relation_entry);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001695 subHeader = relation.getName();
Paul Soulos97f27802014-09-08 13:55:45 -07001696 entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header,
1697 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001698 if (relation.hasKindTypeColumn(kind)) {
Paul Soulos23e28362014-08-29 14:57:08 -07001699 text = Relation.getTypeLabel(res,
Brian Attwell6bb01342014-08-20 23:16:00 -07001700 relation.getKindTypeColumn(kind),
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001701 relation.getLabel()).toString();
1702 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001703 } else if (dataItem instanceof PhoneDataItem) {
1704 final PhoneDataItem phone = (PhoneDataItem) dataItem;
Tyler Gunn5f87e922015-08-05 14:24:52 -07001705 String phoneLabel = null;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001706 if (!TextUtils.isEmpty(phone.getNumber())) {
Paul Soulos23e28362014-08-29 14:57:08 -07001707 primaryContentDescription.append(res.getString(R.string.call_other)).append(" ");
Brian Attwell3bcf09e2014-12-08 16:16:05 -08001708 header = sBidiFormatter.unicodeWrap(phone.buildDataStringForDisplay(context, kind),
Brian Attwellc62cc792014-10-02 12:35:07 -07001709 TextDirectionHeuristics.LTR);
Paul Soulos2a4207f2014-07-31 17:09:05 -07001710 entryContextMenuInfo = new EntryContextMenuInfo(header,
Paul Soulos97f27802014-09-08 13:55:45 -07001711 res.getString(R.string.phoneLabelsGroup), dataItem.getMimeType(),
1712 dataItem.getId(), dataItem.isSuperPrimary());
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001713 if (phone.hasKindTypeColumn(kind)) {
Tingting Wang01b37682015-07-17 14:09:19 -07001714 final int kindTypeColumn = phone.getKindTypeColumn(kind);
1715 final String label = phone.getLabel();
Tyler Gunn5f87e922015-08-05 14:24:52 -07001716 phoneLabel = label;
Tingting Wang01b37682015-07-17 14:09:19 -07001717 if (kindTypeColumn == Phone.TYPE_CUSTOM && TextUtils.isEmpty(label)) {
1718 text = "";
1719 } else {
1720 text = Phone.getTypeLabel(res, kindTypeColumn, label).toString();
Tyler Gunn5f87e922015-08-05 14:24:52 -07001721 phoneLabel= text;
Tingting Wang3df785b2015-07-07 16:53:14 -07001722 primaryContentDescription.append(text).append(" ");
1723 }
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001724 }
Paul Soulos23e28362014-08-29 14:57:08 -07001725 primaryContentDescription.append(header);
Tingting Wangb6949dc2015-11-03 13:17:12 -08001726 phoneContentDescription = com.android.contacts.common.util.ContactDisplayUtils
1727 .getTelephoneTtsSpannable(primaryContentDescription.toString(), header);
Paul Soulos23e28362014-08-29 14:57:08 -07001728 icon = res.getDrawable(R.drawable.ic_phone_24dp);
Paul Soulos48290be2014-09-08 13:44:51 -07001729 iconResourceId = R.drawable.ic_phone_24dp;
Brian Attwell6bb01342014-08-20 23:16:00 -07001730 if (PhoneCapabilityTester.isPhone(context)) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07001731 intent = CallUtil.getCallIntent(phone.getNumber());
yaolu79525d02016-08-24 12:08:39 -07001732 intent.putExtra(EXTRA_ACTION_TYPE, ActionType.CALL);
Paul Soulos16339332014-06-24 13:59:56 -07001733 }
Paul Soulosdd7419d2014-07-15 11:22:13 -07001734 alternateIntent = new Intent(Intent.ACTION_SENDTO,
Jay Shrauner1cd88e32014-09-05 15:37:55 -07001735 Uri.fromParts(ContactsUtils.SCHEME_SMSTO, phone.getNumber(), null));
yaolu79525d02016-08-24 12:08:39 -07001736 alternateIntent.putExtra(EXTRA_ACTION_TYPE, ActionType.SMS);
Paul Soulos48fc9122014-08-26 13:52:36 -07001737
Walter Jang3133a532016-05-04 17:51:19 -07001738 alternateIcon = res.getDrawable(R.drawable.ic_message_24dp_mirrored);
Paul Soulos23e28362014-08-29 14:57:08 -07001739 alternateContentDescription.append(res.getString(R.string.sms_custom, header));
Wenyi Wang5da55ff2015-11-19 13:22:40 -08001740 smsContentDescription = com.android.contacts.common.util.ContactDisplayUtils
1741 .getTelephoneTtsSpannable(alternateContentDescription.toString(), header);
Paul Soulos48fc9122014-08-26 13:52:36 -07001742
Tyler Gunn0a8f9732015-12-15 15:38:54 -08001743 int videoCapability = CallUtil.getVideoCallingAvailability(context);
1744 boolean isPresenceEnabled =
1745 (videoCapability & CallUtil.VIDEO_CALLING_PRESENCE) != 0;
1746 boolean isVideoEnabled = (videoCapability & CallUtil.VIDEO_CALLING_ENABLED) != 0;
1747
Tyler Gunn5f87e922015-08-05 14:24:52 -07001748 if (CallUtil.isCallWithSubjectSupported(context)) {
1749 thirdIcon = res.getDrawable(R.drawable.ic_call_note_white_24dp);
1750 thirdAction = Entry.ACTION_CALL_WITH_SUBJECT;
1751 thirdContentDescription =
1752 res.getString(R.string.call_with_a_note);
Tyler Gunn5f87e922015-08-05 14:24:52 -07001753 // Create a bundle containing the data the call subject dialog requires.
1754 thirdExtras = new Bundle();
1755 thirdExtras.putLong(CallSubjectDialog.ARG_PHOTO_ID,
1756 contactData.getPhotoId());
1757 thirdExtras.putParcelable(CallSubjectDialog.ARG_PHOTO_URI,
1758 UriUtils.parseUriOrNull(contactData.getPhotoUri()));
1759 thirdExtras.putParcelable(CallSubjectDialog.ARG_CONTACT_URI,
1760 contactData.getLookupUri());
1761 thirdExtras.putString(CallSubjectDialog.ARG_NAME_OR_NUMBER,
1762 contactData.getDisplayName());
1763 thirdExtras.putBoolean(CallSubjectDialog.ARG_IS_BUSINESS, false);
1764 thirdExtras.putString(CallSubjectDialog.ARG_NUMBER,
1765 phone.getNumber());
1766 thirdExtras.putString(CallSubjectDialog.ARG_DISPLAY_NUMBER,
1767 phone.getFormattedPhoneNumber());
1768 thirdExtras.putString(CallSubjectDialog.ARG_NUMBER_LABEL,
1769 phoneLabel);
Tyler Gunn0a8f9732015-12-15 15:38:54 -08001770 } else if (isVideoEnabled) {
1771 // Check to ensure carrier presence indicates the number supports video calling.
1772 int carrierPresence = dataItem.getCarrierPresence();
1773 boolean isPresent = (carrierPresence & Phone.CARRIER_PRESENCE_VT_CAPABLE) != 0;
1774
1775 if ((isPresenceEnabled && isPresent) || !isPresenceEnabled) {
1776 thirdIcon = res.getDrawable(R.drawable.ic_videocam);
1777 thirdAction = Entry.ACTION_INTENT;
1778 thirdIntent = CallUtil.getVideoCallIntent(phone.getNumber(),
1779 CALL_ORIGIN_QUICK_CONTACTS_ACTIVITY);
yaolu79525d02016-08-24 12:08:39 -07001780 thirdIntent.putExtra(EXTRA_ACTION_TYPE, ActionType.VIDEOCALL);
Tyler Gunn0a8f9732015-12-15 15:38:54 -08001781 thirdContentDescription =
1782 res.getString(R.string.description_video_call);
1783 }
Paul Soulos48fc9122014-08-26 13:52:36 -07001784 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001785 }
1786 } else if (dataItem instanceof EmailDataItem) {
1787 final EmailDataItem email = (EmailDataItem) dataItem;
1788 final String address = email.getData();
1789 if (!TextUtils.isEmpty(address)) {
Paul Soulos23e28362014-08-29 14:57:08 -07001790 primaryContentDescription.append(res.getString(R.string.email_other)).append(" ");
Jay Shrauner1cd88e32014-09-05 15:37:55 -07001791 final Uri mailUri = Uri.fromParts(ContactsUtils.SCHEME_MAILTO, address, null);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001792 intent = new Intent(Intent.ACTION_SENDTO, mailUri);
yaolu79525d02016-08-24 12:08:39 -07001793 intent.putExtra(EXTRA_ACTION_TYPE, ActionType.EMAIL);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001794 header = email.getAddress();
Paul Soulos2a4207f2014-07-31 17:09:05 -07001795 entryContextMenuInfo = new EntryContextMenuInfo(header,
Paul Soulos97f27802014-09-08 13:55:45 -07001796 res.getString(R.string.emailLabelsGroup), dataItem.getMimeType(),
1797 dataItem.getId(), dataItem.isSuperPrimary());
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001798 if (email.hasKindTypeColumn(kind)) {
Paul Soulos23e28362014-08-29 14:57:08 -07001799 text = Email.getTypeLabel(res, email.getKindTypeColumn(kind),
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001800 email.getLabel()).toString();
Paul Soulos23e28362014-08-29 14:57:08 -07001801 primaryContentDescription.append(text).append(" ");
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001802 }
Paul Soulos23e28362014-08-29 14:57:08 -07001803 primaryContentDescription.append(header);
1804 icon = res.getDrawable(R.drawable.ic_email_24dp);
Paul Soulos48290be2014-09-08 13:44:51 -07001805 iconResourceId = R.drawable.ic_email_24dp;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001806 }
1807 } else if (dataItem instanceof StructuredPostalDataItem) {
1808 StructuredPostalDataItem postal = (StructuredPostalDataItem) dataItem;
1809 final String postalAddress = postal.getFormattedAddress();
1810 if (!TextUtils.isEmpty(postalAddress)) {
Paul Soulos23e28362014-08-29 14:57:08 -07001811 primaryContentDescription.append(res.getString(R.string.map_other)).append(" ");
Paul Souloseb64a4b2014-07-07 17:03:27 -07001812 intent = StructuredPostalUtils.getViewPostalAddressIntent(postalAddress);
yaolu7a09c702016-09-01 18:12:03 -07001813 intent.putExtra(EXTRA_ACTION_TYPE, ActionType.ADDRESS);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001814 header = postal.getFormattedAddress();
Paul Soulos2a4207f2014-07-31 17:09:05 -07001815 entryContextMenuInfo = new EntryContextMenuInfo(header,
Paul Soulos97f27802014-09-08 13:55:45 -07001816 res.getString(R.string.postalLabelsGroup), dataItem.getMimeType(),
1817 dataItem.getId(), dataItem.isSuperPrimary());
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001818 if (postal.hasKindTypeColumn(kind)) {
Paul Soulos23e28362014-08-29 14:57:08 -07001819 text = StructuredPostal.getTypeLabel(res,
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001820 postal.getKindTypeColumn(kind), postal.getLabel()).toString();
Paul Soulos23e28362014-08-29 14:57:08 -07001821 primaryContentDescription.append(text).append(" ");
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001822 }
Paul Soulos23e28362014-08-29 14:57:08 -07001823 primaryContentDescription.append(header);
Paul Soulos6a4d2732014-07-22 14:22:12 -07001824 alternateIntent =
1825 StructuredPostalUtils.getViewPostalAddressDirectionsIntent(postalAddress);
yaolu79525d02016-08-24 12:08:39 -07001826 alternateIntent.putExtra(EXTRA_ACTION_TYPE, ActionType.DIRECTIONS);
Paul Soulos23e28362014-08-29 14:57:08 -07001827 alternateIcon = res.getDrawable(R.drawable.ic_directions_24dp);
1828 alternateContentDescription.append(res.getString(
1829 R.string.content_description_directions)).append(" ").append(header);
1830 icon = res.getDrawable(R.drawable.ic_place_24dp);
Paul Soulos48290be2014-09-08 13:44:51 -07001831 iconResourceId = R.drawable.ic_place_24dp;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001832 }
1833 } else if (dataItem instanceof SipAddressDataItem) {
Brian Attwell593e5a72014-10-22 19:45:28 -07001834 final SipAddressDataItem sip = (SipAddressDataItem) dataItem;
1835 final String address = sip.getSipAddress();
1836 if (!TextUtils.isEmpty(address)) {
1837 primaryContentDescription.append(res.getString(R.string.call_other)).append(
1838 " ");
1839 if (PhoneCapabilityTester.isSipPhone(context)) {
Jay Shrauner1cd88e32014-09-05 15:37:55 -07001840 final Uri callUri = Uri.fromParts(PhoneAccount.SCHEME_SIP, address, null);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001841 intent = CallUtil.getCallIntent(callUri);
yaolu79525d02016-08-24 12:08:39 -07001842 intent.putExtra(EXTRA_ACTION_TYPE, ActionType.SIPCALL);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001843 }
Brian Attwell593e5a72014-10-22 19:45:28 -07001844 header = address;
1845 entryContextMenuInfo = new EntryContextMenuInfo(header,
1846 res.getString(R.string.phoneLabelsGroup), dataItem.getMimeType(),
1847 dataItem.getId(), dataItem.isSuperPrimary());
1848 if (sip.hasKindTypeColumn(kind)) {
1849 text = SipAddress.getTypeLabel(res,
1850 sip.getKindTypeColumn(kind), sip.getLabel()).toString();
1851 primaryContentDescription.append(text).append(" ");
1852 }
1853 primaryContentDescription.append(header);
1854 icon = res.getDrawable(R.drawable.ic_dialer_sip_black_24dp);
1855 iconResourceId = R.drawable.ic_dialer_sip_black_24dp;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001856 }
1857 } else if (dataItem instanceof StructuredNameDataItem) {
Walter Jang8025f802015-02-27 14:18:14 -08001858 // If the name is already set and this is not the super primary value then leave the
1859 // current value. This way we show the super primary value when we are able to.
1860 if (dataItem.isSuperPrimary() || aboutCardName.value == null
1861 || aboutCardName.value.isEmpty()) {
1862 final String givenName = ((StructuredNameDataItem) dataItem).getGivenName();
1863 if (!TextUtils.isEmpty(givenName)) {
1864 aboutCardName.value = res.getString(R.string.about_card_title) +
1865 " " + givenName;
1866 } else {
1867 aboutCardName.value = res.getString(R.string.about_card_title);
1868 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001869 }
1870 } else {
1871 // Custom DataItem
Brian Attwell6bb01342014-08-20 23:16:00 -07001872 header = dataItem.buildDataStringForDisplay(context, kind);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001873 text = kind.typeColumn;
1874 intent = new Intent(Intent.ACTION_VIEW);
Paul Soulos60e51082014-07-10 12:33:04 -07001875 final Uri uri = ContentUris.withAppendedId(Data.CONTENT_URI, dataItem.getId());
1876 intent.setDataAndType(uri, dataItem.getMimeType());
yaolu79525d02016-08-24 12:08:39 -07001877 intent.putExtra(EXTRA_ACTION_TYPE, ActionType.THIRD_PARTY);
yaolua92b2d12016-09-21 17:09:23 -07001878 intent.putExtra(EXTRA_THIRD_PARTY_ACTION, dataItem.getMimeType());
Paul Soulose0055662014-07-10 16:33:08 -07001879
1880 if (intent != null) {
1881 final String mimetype = intent.getType();
1882
Paul Soulos6a2a1a72014-10-10 15:52:56 -07001883 // Build advanced entry for known 3p types. Otherwise default to ResolveCache icon.
yaolu8c3ff2c2016-08-18 20:13:40 -07001884 if (MIMETYPE_HANGOUTS.equals(mimetype)) {
1885 // If a secondDataItem is available, use it to build an entry with
1886 // alternate actions
1887 if (secondDataItem != null) {
1888 icon = res.getDrawable(R.drawable.ic_hangout_24dp);
1889 alternateIcon = res.getDrawable(R.drawable.ic_hangout_video_24dp);
1890 final HangoutsDataItemModel itemModel =
1891 new HangoutsDataItemModel(intent, alternateIntent,
1892 dataItem, secondDataItem, alternateContentDescription,
1893 header, text, context);
Paul Soulos6a2a1a72014-10-10 15:52:56 -07001894
yaolu8c3ff2c2016-08-18 20:13:40 -07001895 populateHangoutsDataItemModel(itemModel);
1896 intent = itemModel.intent;
1897 alternateIntent = itemModel.alternateIntent;
1898 alternateContentDescription = itemModel.alternateContentDescription;
1899 header = itemModel.header;
1900 text = itemModel.text;
1901 } else {
1902 if (HANGOUTS_DATA_5_VIDEO.equals(intent.getDataString())) {
1903 icon = res.getDrawable(R.drawable.ic_hangout_video_24dp);
Paul Soulos6a2a1a72014-10-10 15:52:56 -07001904 } else {
yaolu8c3ff2c2016-08-18 20:13:40 -07001905 icon = res.getDrawable(R.drawable.ic_hangout_24dp);
Paul Soulose0055662014-07-10 16:33:08 -07001906 }
yaolu8c3ff2c2016-08-18 20:13:40 -07001907 }
1908 } else {
1909 icon = ResolveCache.getInstance(context).getIcon(
1910 dataItem.getMimeType(), intent);
1911 // Call mutate to create a new Drawable.ConstantState for color filtering
1912 if (icon != null) {
1913 icon.mutate();
1914 }
1915 shouldApplyColor = false;
1916
1917 if (!MIMETYPE_GPLUS_PROFILE.equals(mimetype)) {
Paul Soulos97f27802014-09-08 13:55:45 -07001918 entryContextMenuInfo = new EntryContextMenuInfo(header, mimetype,
1919 dataItem.getMimeType(), dataItem.getId(),
1920 dataItem.isSuperPrimary());
yaolu8c3ff2c2016-08-18 20:13:40 -07001921 }
Paul Soulose0055662014-07-10 16:33:08 -07001922 }
1923 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001924 }
1925
Paul Souloseb64a4b2014-07-07 17:03:27 -07001926 if (intent != null) {
1927 // Do not set the intent is there are no resolves
Brian Attwell6bb01342014-08-20 23:16:00 -07001928 if (!PhoneCapabilityTester.isIntentRegistered(context, intent)) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07001929 intent = null;
Paul Soulos16339332014-06-24 13:59:56 -07001930 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001931 }
Paul Soulos16339332014-06-24 13:59:56 -07001932
Paul Soulosdd7419d2014-07-15 11:22:13 -07001933 if (alternateIntent != null) {
1934 // Do not set the alternate intent is there are no resolves
Brian Attwell6bb01342014-08-20 23:16:00 -07001935 if (!PhoneCapabilityTester.isIntentRegistered(context, alternateIntent)) {
Paul Soulosdd7419d2014-07-15 11:22:13 -07001936 alternateIntent = null;
Jay Shraunerf0d04c12014-09-02 13:44:48 -07001937 } else if (TextUtils.isEmpty(alternateContentDescription)) {
1938 // Attempt to use package manager to find a suitable content description if needed
Paul Soulos23e28362014-08-29 14:57:08 -07001939 alternateContentDescription.append(getIntentResolveLabel(alternateIntent, context));
Paul Soulosdd7419d2014-07-15 11:22:13 -07001940 }
1941 }
1942
Paul Souloseb64a4b2014-07-07 17:03:27 -07001943 // If the Entry has no visual elements, return null
1944 if (icon == null && TextUtils.isEmpty(header) && TextUtils.isEmpty(subHeader) &&
1945 subHeaderIcon == null && TextUtils.isEmpty(text) && textIcon == null) {
1946 return null;
1947 }
1948
Brian Attwellfa238942014-08-12 10:21:28 -07001949 // Ignore dataIds from the Me profile.
Paul Soulosea5e0b72014-07-08 18:09:44 -07001950 final int dataId = dataItem.getId() > Integer.MAX_VALUE ?
1951 -1 : (int) dataItem.getId();
1952
Paul Soulos23e28362014-08-29 14:57:08 -07001953 return new Entry(dataId, icon, header, subHeader, subHeaderIcon, text, textIcon,
Tingting Wangb6949dc2015-11-03 13:17:12 -08001954 phoneContentDescription == null
1955 ? new SpannableString(primaryContentDescription.toString())
1956 : phoneContentDescription,
Walter Jang7ce53522014-10-29 13:26:43 -07001957 intent, alternateIcon, alternateIntent,
Wenyi Wang5da55ff2015-11-19 13:22:40 -08001958 smsContentDescription == null
1959 ? new SpannableString(alternateContentDescription.toString())
1960 : smsContentDescription,
1961 shouldApplyColor, isEditable,
Tyler Gunn5f87e922015-08-05 14:24:52 -07001962 entryContextMenuInfo, thirdIcon, thirdIntent, thirdContentDescription, thirdAction,
1963 thirdExtras, iconResourceId);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001964 }
1965
Brian Attwell6bb01342014-08-20 23:16:00 -07001966 private List<Entry> dataItemsToEntries(List<DataItem> dataItems,
1967 MutableString aboutCardTitleOut) {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07001968 // Hangouts and G+ use two data items to create one entry.
yaolu3904fb52016-08-05 13:32:56 -07001969 if (dataItems.get(0).getMimeType().equals(MIMETYPE_GPLUS_PROFILE)) {
1970 return gPlusDataItemsToEntries(dataItems);
1971 } else if (dataItems.get(0).getMimeType().equals(MIMETYPE_HANGOUTS)) {
1972 return hangoutsDataItemsToEntries(dataItems);
Paul Soulos6a2a1a72014-10-10 15:52:56 -07001973 } else {
1974 final List<Entry> entries = new ArrayList<>();
1975 for (DataItem dataItem : dataItems) {
1976 final Entry entry = dataItemToEntry(dataItem, /* secondDataItem = */ null,
1977 this, mContactData, aboutCardTitleOut);
1978 if (entry != null) {
1979 entries.add(entry);
1980 }
1981 }
1982 return entries;
1983 }
1984 }
1985
1986 /**
yaolu3904fb52016-08-05 13:32:56 -07001987 * Put the data items into buckets based on the raw contact id
Paul Soulos6a2a1a72014-10-10 15:52:56 -07001988 */
yaolu3904fb52016-08-05 13:32:56 -07001989 private Map<Long, List<DataItem>> dataItemsToBucket(List<DataItem> dataItems) {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07001990 final Map<Long, List<DataItem>> buckets = new HashMap<>();
Paul Souloseb64a4b2014-07-07 17:03:27 -07001991 for (DataItem dataItem : dataItems) {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07001992 List<DataItem> bucket = buckets.get(dataItem.getRawContactId());
1993 if (bucket == null) {
1994 bucket = new ArrayList<>();
1995 buckets.put(dataItem.getRawContactId(), bucket);
1996 }
1997 bucket.add(dataItem);
1998 }
yaolu3904fb52016-08-05 13:32:56 -07001999 return buckets;
2000 }
2001
2002 /**
2003 * For G+ entries, a single ExpandingEntryCardView.Entry consists of two data items. This
2004 * method use only the View profile to build entry.
2005 */
2006 private List<Entry> gPlusDataItemsToEntries(List<DataItem> dataItems) {
2007 final List<Entry> entries = new ArrayList<>();
2008
2009 for (List<DataItem> bucket : dataItemsToBucket(dataItems).values()) {
2010 for (DataItem dataItem : bucket) {
2011 if (GPLUS_PROFILE_DATA_5_VIEW_PROFILE.equals(
2012 dataItem.getContentValues().getAsString(Data.DATA5))) {
2013 final Entry entry = dataItemToEntry(dataItem, /* secondDataItem = */ null,
2014 this, mContactData, /* aboutCardName = */ null);
2015 if (entry != null) {
2016 entries.add(entry);
2017 }
2018 }
2019 }
2020 }
2021 return entries;
2022 }
2023
2024 /**
2025 * For Hangouts entries, a single ExpandingEntryCardView.Entry consists of two data items. This
2026 * method attempts to build each entry using the two data items if they are available. If there
2027 * are more or less than two data items, a fall back is used and each data item gets its own
2028 * entry.
2029 */
2030 private List<Entry> hangoutsDataItemsToEntries(List<DataItem> dataItems) {
2031 final List<Entry> entries = new ArrayList<>();
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002032
2033 // Use the buckets to build entries. If a bucket contains two data items, build the special
2034 // entry, otherwise fall back to the normal entry.
yaolu3904fb52016-08-05 13:32:56 -07002035 for (List<DataItem> bucket : dataItemsToBucket(dataItems).values()) {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002036 if (bucket.size() == 2) {
2037 // Use the pair to build an entry
2038 final Entry entry = dataItemToEntry(bucket.get(0),
2039 /* secondDataItem = */ bucket.get(1), this, mContactData,
2040 /* aboutCardName = */ null);
2041 if (entry != null) {
2042 entries.add(entry);
2043 }
2044 } else {
2045 for (DataItem dataItem : bucket) {
2046 final Entry entry = dataItemToEntry(dataItem, /* secondDataItem = */ null,
2047 this, mContactData, /* aboutCardName = */ null);
2048 if (entry != null) {
2049 entries.add(entry);
2050 }
2051 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07002052 }
2053 }
2054 return entries;
Daniel Lehmannedb576a2011-07-27 16:45:13 -07002055 }
2056
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002057 /**
yaolu3904fb52016-08-05 13:32:56 -07002058 * Used for statically passing around Hangouts data items and entry fields to
2059 * populateHangoutsDataItemModel.
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002060 */
yaolu3904fb52016-08-05 13:32:56 -07002061 private static final class HangoutsDataItemModel {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002062 public Intent intent;
2063 public Intent alternateIntent;
2064 public DataItem dataItem;
2065 public DataItem secondDataItem;
2066 public StringBuilder alternateContentDescription;
2067 public String header;
2068 public String text;
2069 public Context context;
2070
yaolu3904fb52016-08-05 13:32:56 -07002071 public HangoutsDataItemModel(Intent intent, Intent alternateIntent, DataItem dataItem,
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002072 DataItem secondDataItem, StringBuilder alternateContentDescription, String header,
2073 String text, Context context) {
2074 this.intent = intent;
2075 this.alternateIntent = alternateIntent;
2076 this.dataItem = dataItem;
2077 this.secondDataItem = secondDataItem;
2078 this.alternateContentDescription = alternateContentDescription;
2079 this.header = header;
2080 this.text = text;
2081 this.context = context;
2082 }
2083 }
2084
yaolu3904fb52016-08-05 13:32:56 -07002085 private static void populateHangoutsDataItemModel(
2086 HangoutsDataItemModel dataModel) {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002087 final Intent secondIntent = new Intent(Intent.ACTION_VIEW);
2088 secondIntent.setDataAndType(ContentUris.withAppendedId(Data.CONTENT_URI,
2089 dataModel.secondDataItem.getId()), dataModel.secondDataItem.getMimeType());
yaolu79525d02016-08-24 12:08:39 -07002090 secondIntent.putExtra(EXTRA_ACTION_TYPE, ActionType.THIRD_PARTY);
yaolua92b2d12016-09-21 17:09:23 -07002091 secondIntent.putExtra(EXTRA_THIRD_PARTY_ACTION, dataModel.secondDataItem.getMimeType());
yaolu79525d02016-08-24 12:08:39 -07002092
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002093 // There is no guarantee the order the data items come in. Second
2094 // data item does not necessarily mean it's the alternate.
yaolu3904fb52016-08-05 13:32:56 -07002095 // Hangouts video should be alternate. Swap if needed
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002096 if (HANGOUTS_DATA_5_VIDEO.equals(
yaolu3904fb52016-08-05 13:32:56 -07002097 dataModel.dataItem.getContentValues().getAsString(Data.DATA5))) {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002098 dataModel.alternateIntent = dataModel.intent;
2099 dataModel.alternateContentDescription = new StringBuilder(dataModel.header);
2100
2101 dataModel.intent = secondIntent;
yaolua92b2d12016-09-21 17:09:23 -07002102 dataModel.header = dataModel.secondDataItem.buildDataStringForDisplay(
2103 dataModel.context, dataModel.secondDataItem.getDataKind());
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002104 dataModel.text = dataModel.secondDataItem.getDataKind().typeColumn;
2105 } else if (HANGOUTS_DATA_5_MESSAGE.equals(
yaolu3904fb52016-08-05 13:32:56 -07002106 dataModel.dataItem.getContentValues().getAsString(Data.DATA5))) {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002107 dataModel.alternateIntent = secondIntent;
2108 dataModel.alternateContentDescription = new StringBuilder(
2109 dataModel.secondDataItem.buildDataStringForDisplay(dataModel.context,
2110 dataModel.secondDataItem.getDataKind()));
2111 }
2112 }
2113
Brian Attwell6bb01342014-08-20 23:16:00 -07002114 private static String getIntentResolveLabel(Intent intent, Context context) {
2115 final List<ResolveInfo> matches = context.getPackageManager().queryIntentActivities(intent,
Paul Soulosdd7419d2014-07-15 11:22:13 -07002116 PackageManager.MATCH_DEFAULT_ONLY);
2117
2118 // Pick first match, otherwise best found
2119 ResolveInfo bestResolve = null;
2120 final int size = matches.size();
2121 if (size == 1) {
2122 bestResolve = matches.get(0);
2123 } else if (size > 1) {
Brian Attwell6bb01342014-08-20 23:16:00 -07002124 bestResolve = ResolveCache.getInstance(context).getBestResolve(intent, matches);
Paul Soulosdd7419d2014-07-15 11:22:13 -07002125 }
2126
2127 if (bestResolve == null) {
2128 return null;
2129 }
2130
Brian Attwell6bb01342014-08-20 23:16:00 -07002131 return String.valueOf(bestResolve.loadLabel(context.getPackageManager()));
Paul Soulosdd7419d2014-07-15 11:22:13 -07002132 }
2133
Daniel Lehmannedb576a2011-07-27 16:45:13 -07002134 /**
Brian Attwell31b2d422014-06-05 00:14:58 -07002135 * Asynchronously extract the most vibrant color from the PhotoView. Once extracted,
2136 * apply this tint to {@link MultiShrinkScroller}. This operation takes about 20-30ms
2137 * on a Nexus 5.
2138 */
2139 private void extractAndApplyTintFromPhotoViewAsynchronously() {
2140 if (mScroller == null) {
2141 return;
2142 }
2143 final Drawable imageViewDrawable = mPhotoView.getDrawable();
Brian Attwellfaf97392014-07-10 18:32:30 -07002144 new AsyncTask<Void, Void, MaterialPalette>() {
Brian Attwell31b2d422014-06-05 00:14:58 -07002145 @Override
Brian Attwellfaf97392014-07-10 18:32:30 -07002146 protected MaterialPalette doInBackground(Void... params) {
2147
Jay Shrauner929a62f2014-11-13 12:03:50 -08002148 if (imageViewDrawable instanceof BitmapDrawable && mContactData != null
Brian Attwell95c268e2014-08-26 22:04:15 -07002149 && mContactData.getThumbnailPhotoBinaryData() != null
2150 && mContactData.getThumbnailPhotoBinaryData().length > 0) {
2151 // Perform the color analysis on the thumbnail instead of the full sized
2152 // image, so that our results will be as similar as possible to the Bugle
2153 // app.
2154 final Bitmap bitmap = BitmapFactory.decodeByteArray(
2155 mContactData.getThumbnailPhotoBinaryData(), 0,
2156 mContactData.getThumbnailPhotoBinaryData().length);
2157 try {
2158 final int primaryColor = colorFromBitmap(bitmap);
2159 if (primaryColor != 0) {
2160 return mMaterialColorMapUtils.calculatePrimaryAndSecondaryColor(
2161 primaryColor);
2162 }
2163 } finally {
2164 bitmap.recycle();
Brian Attwellfaf97392014-07-10 18:32:30 -07002165 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002166 }
2167 if (imageViewDrawable instanceof LetterTileDrawable) {
Brian Attwellfaf97392014-07-10 18:32:30 -07002168 final int primaryColor = ((LetterTileDrawable) imageViewDrawable).getColor();
Brian Attwella3859ed2014-07-15 13:51:55 -07002169 return mMaterialColorMapUtils.calculatePrimaryAndSecondaryColor(primaryColor);
Brian Attwell31b2d422014-06-05 00:14:58 -07002170 }
Brian Attwella3859ed2014-07-15 13:51:55 -07002171 return MaterialColorMapUtils.getDefaultPrimaryAndSecondaryColors(getResources());
Brian Attwell31b2d422014-06-05 00:14:58 -07002172 }
2173
2174 @Override
Brian Attwellfaf97392014-07-10 18:32:30 -07002175 protected void onPostExecute(MaterialPalette palette) {
2176 super.onPostExecute(palette);
Brian Attwell8571dd32014-06-23 23:29:10 -07002177 if (mHasComputedThemeColor) {
2178 // If we had previously computed a theme color from the contact photo,
2179 // then do not update the theme color. Changing the theme color several
2180 // seconds after QC has started, as a result of an updated/upgraded photo,
2181 // is a jarring experience. On the other hand, changing the theme color after
2182 // a rotation or onNewIntent() is perfectly fine.
2183 return;
2184 }
2185 // Check that the Photo has not changed. If it has changed, the new tint
2186 // color needs to be extracted
2187 if (imageViewDrawable == mPhotoView.getDrawable()) {
2188 mHasComputedThemeColor = true;
Brian Attwellfaf97392014-07-10 18:32:30 -07002189 setThemeColor(palette);
Brian Attwell31b2d422014-06-05 00:14:58 -07002190 }
2191 }
2192 }.execute();
2193 }
2194
Brian Attwellfaf97392014-07-10 18:32:30 -07002195 private void setThemeColor(MaterialPalette palette) {
Brian Attwell9b889e62014-06-23 18:25:32 -07002196 // If the color is invalid, use the predefined default
Brian Attwell8e29faf2015-01-21 10:22:40 -08002197 mColorFilterColor = palette.mPrimaryColor;
2198 mScroller.setHeaderTintColor(mColorFilterColor);
Brian Attwellfaf97392014-07-10 18:32:30 -07002199 mStatusBarColor = palette.mSecondaryColor;
Brian Attwell9b889e62014-06-23 18:25:32 -07002200 updateStatusBarColor();
Brian Attwell8571dd32014-06-23 23:29:10 -07002201
Brian Attwell9b889e62014-06-23 18:25:32 -07002202 mColorFilter =
Brian Attwell8e29faf2015-01-21 10:22:40 -08002203 new PorterDuffColorFilter(mColorFilterColor, PorterDuff.Mode.SRC_ATOP);
2204 mContactCard.setColorAndFilter(mColorFilterColor, mColorFilter);
2205 mRecentCard.setColorAndFilter(mColorFilterColor, mColorFilter);
2206 mAboutCard.setColorAndFilter(mColorFilterColor, mColorFilter);
Brian Attwell9b889e62014-06-23 18:25:32 -07002207 }
2208
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002209 private void updateStatusBarColor() {
Wenyi Wang4c3d3e22015-12-17 14:30:02 -08002210 if (mScroller == null || !CompatUtils.isLollipopCompatible()) {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002211 return;
2212 }
2213 final int desiredStatusBarColor;
2214 // Only use a custom status bar color if QuickContacts touches the top of the viewport.
2215 if (mScroller.getScrollNeededToBeFullScreen() <= 0) {
2216 desiredStatusBarColor = mStatusBarColor;
2217 } else {
2218 desiredStatusBarColor = Color.TRANSPARENT;
2219 }
2220 // Animate to the new color.
Brian Attwell847bf2c2014-07-24 13:59:27 -07002221 final ObjectAnimator animation = ObjectAnimator.ofInt(getWindow(), "statusBarColor",
2222 getWindow().getStatusBarColor(), desiredStatusBarColor);
2223 animation.setDuration(ANIMATION_STATUS_BAR_COLOR_CHANGE_DURATION);
2224 animation.setEvaluator(new ArgbEvaluator());
2225 animation.start();
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002226 }
2227
2228 private int colorFromBitmap(Bitmap bitmap) {
2229 // Author of Palette recommends using 24 colors when analyzing profile photos.
2230 final int NUMBER_OF_PALETTE_COLORS = 24;
2231 final Palette palette = Palette.generate(bitmap, NUMBER_OF_PALETTE_COLORS);
Brian Attwella0f20f72014-07-07 11:51:52 -07002232 if (palette != null && palette.getVibrantSwatch() != null) {
2233 return palette.getVibrantSwatch().getRgb();
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002234 }
2235 return 0;
2236 }
2237
Paul Soulosb3054e52014-06-05 16:46:02 -07002238 private List<Entry> contactInteractionsToEntries(List<ContactInteraction> interactions) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07002239 final List<Entry> entries = new ArrayList<>();
Paul Soulosb3054e52014-06-05 16:46:02 -07002240 for (ContactInteraction interaction : interactions) {
Brian Attwelled188282015-02-11 14:12:41 -08002241 if (interaction == null) {
2242 continue;
2243 }
Paul Soulosea5e0b72014-07-08 18:09:44 -07002244 entries.add(new Entry(/* id = */ -1,
2245 interaction.getIcon(this),
Paul Soulosb3054e52014-06-05 16:46:02 -07002246 interaction.getViewHeader(this),
2247 interaction.getViewBody(this),
2248 interaction.getBodyIcon(this),
2249 interaction.getViewFooter(this),
2250 interaction.getFooterIcon(this),
Paul Soulos23e28362014-08-29 14:57:08 -07002251 interaction.getContentDescription(this),
Paul Soulosb3054e52014-06-05 16:46:02 -07002252 interaction.getIntent(),
Paul Soulosdd7419d2014-07-15 11:22:13 -07002253 /* alternateIcon = */ null,
2254 /* alternateIntent = */ null,
2255 /* alternateContentDescription = */ null,
Paul Soulos48ebbaa2014-07-15 13:11:23 -07002256 /* shouldApplyColor = */ true,
Paul Soulos2a4207f2014-07-31 17:09:05 -07002257 /* isEditable = */ false,
Paul Soulos48fc9122014-08-26 13:52:36 -07002258 /* EntryContextMenuInfo = */ null,
2259 /* thirdIcon = */ null,
2260 /* thirdIntent = */ null,
Paul Soulos48290be2014-09-08 13:44:51 -07002261 /* thirdContentDescription = */ null,
Tyler Gunn5f87e922015-08-05 14:24:52 -07002262 /* thirdAction = */ Entry.ACTION_NONE,
2263 /* thirdActionExtras = */ null,
Paul Soulos48290be2014-09-08 13:44:51 -07002264 interaction.getIconResourceId()));
Paul Soulosb3054e52014-06-05 16:46:02 -07002265 }
2266 return entries;
2267 }
2268
Paul Souloseb64a4b2014-07-07 17:03:27 -07002269 private final LoaderCallbacks<Contact> mLoaderContactCallbacks =
Maurice Chu851222a2012-06-21 11:43:08 -07002270 new LoaderCallbacks<Contact>() {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002271 @Override
Maurice Chu851222a2012-06-21 11:43:08 -07002272 public void onLoaderReset(Loader<Contact> loader) {
Paul Soulos405ae402014-07-14 16:16:36 -07002273 mContactData = null;
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002274 }
2275
2276 @Override
Maurice Chu851222a2012-06-21 11:43:08 -07002277 public void onLoadFinished(Loader<Contact> loader, Contact data) {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002278 Trace.beginSection("onLoadFinished()");
Brian Attwell930da3a2014-10-16 21:01:21 -07002279 try {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002280
Brian Attwell930da3a2014-10-16 21:01:21 -07002281 if (isFinishing()) {
2282 return;
2283 }
2284 if (data.isError()) {
Jay Shrauner02ecc3f2014-12-12 11:29:16 -08002285 // This means either the contact is invalid or we had an
2286 // internal error such as an acore crash.
2287 Log.i(TAG, "Failed to load contact: " + ((ContactLoader)loader).getLookupUri());
2288 Toast.makeText(QuickContactActivity.this, R.string.invalidContactMessage,
2289 Toast.LENGTH_LONG).show();
2290 finish();
2291 return;
Brian Attwell930da3a2014-10-16 21:01:21 -07002292 }
2293 if (data.isNotFound()) {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002294 Log.i(TAG, "No contact found: " + ((ContactLoader)loader).getLookupUri());
2295 Toast.makeText(QuickContactActivity.this, R.string.invalidContactMessage,
2296 Toast.LENGTH_LONG).show();
Brian Attwell930da3a2014-10-16 21:01:21 -07002297 finish();
2298 return;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002299 }
Brian Attwell930da3a2014-10-16 21:01:21 -07002300
Marcus Hagerott677ee2b2016-10-28 15:31:55 -07002301 if (!mIsRecreatedInstance && !mShortcutUsageReported && data != null) {
Marcus Hagerottd105c1e2016-09-30 14:28:00 -07002302 mShortcutUsageReported = true;
2303 DynamicShortcuts.reportShortcutUsed(QuickContactActivity.this,
2304 data.getLookupKey());
2305 }
Brian Attwell930da3a2014-10-16 21:01:21 -07002306 bindContactData(data);
2307
2308 } finally {
2309 Trace.endSection();
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002310 }
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002311 }
2312
2313 @Override
Maurice Chu851222a2012-06-21 11:43:08 -07002314 public Loader<Contact> onCreateLoader(int id, Bundle args) {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002315 if (mLookupUri == null) {
2316 Log.wtf(TAG, "Lookup uri wasn't initialized. Loader was started too early");
2317 }
Brian Attwelld28851f2014-06-10 13:25:07 -07002318 // Load all contact data. We need loadGroupMetaData=true to determine whether the
2319 // contact is invisible. If it is, we need to display an "Add to Contacts" MenuItem.
Yorke Leeb2b435a2012-11-12 16:47:06 -08002320 return new ContactLoader(getApplicationContext(), mLookupUri,
Brian Attwelld28851f2014-06-10 13:25:07 -07002321 true /*loadGroupMetaData*/, false /*loadInvitableAccountTypes*/,
Brian Attwell8571dd32014-06-23 23:29:10 -07002322 true /*postViewNotification*/, true /*computeFormattedPhoneNumber*/);
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002323 }
2324 };
Paul Soulosb3054e52014-06-05 16:46:02 -07002325
Brian Attwellb7e43642014-06-02 14:33:04 -07002326 @Override
2327 public void onBackPressed() {
Tingting Wangac9596e2016-08-02 22:24:24 -07002328 final int previousScreenType = getIntent().getIntExtra
2329 (EXTRA_PREVIOUS_SCREEN_TYPE, ScreenType.UNKNOWN);
2330 if ((previousScreenType == ScreenType.ALL_CONTACTS
2331 || previousScreenType == ScreenType.FAVORITES)
2332 && !SharedPreferenceUtil.getHamburgerPromoTriggerActionHappenedBefore(this)) {
2333 SharedPreferenceUtil.setHamburgerPromoTriggerActionHappenedBefore(this);
2334 }
Brian Attwellb7e43642014-06-02 14:33:04 -07002335 if (mScroller != null) {
Brian Attwell8477eaf2014-06-18 15:39:50 -07002336 if (!mIsExitAnimationInProgress) {
2337 mScroller.scrollOffBottom();
2338 }
Brian Attwellb7e43642014-06-02 14:33:04 -07002339 } else {
2340 super.onBackPressed();
2341 }
2342 }
Paul Soulosb3054e52014-06-05 16:46:02 -07002343
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002344 @Override
2345 public void finish() {
2346 super.finish();
2347
2348 // override transitions to skip the standard window animations
2349 overridePendingTransition(0, 0);
2350 }
2351
Paul Souloseb64a4b2014-07-07 17:03:27 -07002352 private final LoaderCallbacks<List<ContactInteraction>> mLoaderInteractionsCallbacks =
Paul Soulosb3054e52014-06-05 16:46:02 -07002353 new LoaderCallbacks<List<ContactInteraction>>() {
2354
2355 @Override
2356 public Loader<List<ContactInteraction>> onCreateLoader(int id, Bundle args) {
Paul Soulosb3054e52014-06-05 16:46:02 -07002357 Loader<List<ContactInteraction>> loader = null;
2358 switch (id) {
2359 case LOADER_SMS_ID:
Paul Soulosb3054e52014-06-05 16:46:02 -07002360 loader = new SmsInteractionsLoader(
2361 QuickContactActivity.this,
Paul Soulosab840442014-06-17 14:08:40 -07002362 args.getStringArray(KEY_LOADER_EXTRA_PHONES),
Paul Soulosb3054e52014-06-05 16:46:02 -07002363 MAX_SMS_RETRIEVE);
2364 break;
Paul Soulos899aa212014-06-11 12:04:43 -07002365 case LOADER_CALENDAR_ID:
Paul Soulosae4cafe2014-07-09 14:11:03 -07002366 final String[] emailsArray = args.getStringArray(KEY_LOADER_EXTRA_EMAILS);
2367 List<String> emailsList = null;
2368 if (emailsArray != null) {
2369 emailsList = Arrays.asList(args.getStringArray(KEY_LOADER_EXTRA_EMAILS));
2370 }
Paul Soulos899aa212014-06-11 12:04:43 -07002371 loader = new CalendarInteractionsLoader(
2372 QuickContactActivity.this,
Paul Soulosae4cafe2014-07-09 14:11:03 -07002373 emailsList,
Paul Soulos899aa212014-06-11 12:04:43 -07002374 MAX_FUTURE_CALENDAR_RETRIEVE,
2375 MAX_PAST_CALENDAR_RETRIEVE,
2376 FUTURE_MILLISECOND_TO_SEARCH_LOCAL_CALENDAR,
2377 PAST_MILLISECOND_TO_SEARCH_LOCAL_CALENDAR);
2378 break;
Paul Soulosab840442014-06-17 14:08:40 -07002379 case LOADER_CALL_LOG_ID:
Paul Soulosab840442014-06-17 14:08:40 -07002380 loader = new CallLogInteractionsLoader(
2381 QuickContactActivity.this,
2382 args.getStringArray(KEY_LOADER_EXTRA_PHONES),
guanxiongliuc7a4b9c2016-04-30 20:19:21 -07002383 args.getStringArray(KEY_LOADER_EXTRA_SIP_NUMBERS),
Paul Soulosab840442014-06-17 14:08:40 -07002384 MAX_CALL_LOG_RETRIEVE);
Paul Soulosb3054e52014-06-05 16:46:02 -07002385 }
2386 return loader;
2387 }
2388
2389 @Override
2390 public void onLoadFinished(Loader<List<ContactInteraction>> loader,
2391 List<ContactInteraction> data) {
Paul Soulosb3054e52014-06-05 16:46:02 -07002392 mRecentLoaderResults.put(loader.getId(), data);
2393
2394 if (isAllRecentDataLoaded()) {
2395 bindRecentData();
2396 }
2397 }
2398
2399 @Override
2400 public void onLoaderReset(Loader<List<ContactInteraction>> loader) {
2401 mRecentLoaderResults.remove(loader.getId());
2402 }
Paul Soulosb3054e52014-06-05 16:46:02 -07002403 };
2404
2405 private boolean isAllRecentDataLoaded() {
2406 return mRecentLoaderResults.size() == mRecentLoaderIds.length;
2407 }
2408
2409 private void bindRecentData() {
Paul Souloseb64a4b2014-07-07 17:03:27 -07002410 final List<ContactInteraction> allInteractions = new ArrayList<>();
Brian Attwell30e1ef12014-09-02 15:49:20 -07002411 final List<List<Entry>> interactionsWrapper = new ArrayList<>();
Paul Soulosb3054e52014-06-05 16:46:02 -07002412
Brian Attwelled188282015-02-11 14:12:41 -08002413 // Serialize mRecentLoaderResults into a single list. This should be done on the main
2414 // thread to avoid races against mRecentLoaderResults edits.
2415 for (List<ContactInteraction> loaderInteractions : mRecentLoaderResults.values()) {
2416 allInteractions.addAll(loaderInteractions);
2417 }
2418
Brian Attwell30e1ef12014-09-02 15:49:20 -07002419 mRecentDataTask = new AsyncTask<Void, Void, Void>() {
Paul Soulosb3054e52014-06-05 16:46:02 -07002420 @Override
Brian Attwell30e1ef12014-09-02 15:49:20 -07002421 protected Void doInBackground(Void... params) {
2422 Trace.beginSection("sort recent loader results");
Paul Soulosb3054e52014-06-05 16:46:02 -07002423
Brian Attwell30e1ef12014-09-02 15:49:20 -07002424 // Sort the interactions by most recent
2425 Collections.sort(allInteractions, new Comparator<ContactInteraction>() {
2426 @Override
2427 public int compare(ContactInteraction a, ContactInteraction b) {
Brian Attwelled188282015-02-11 14:12:41 -08002428 if (a == null && b == null) {
2429 return 0;
2430 }
2431 if (a == null) {
2432 return 1;
2433 }
2434 if (b == null) {
2435 return -1;
2436 }
2437 if (a.getInteractionDate() > b.getInteractionDate()) {
2438 return -1;
2439 }
2440 if (a.getInteractionDate() == b.getInteractionDate()) {
2441 return 0;
2442 }
2443 return 1;
Brian Attwell30e1ef12014-09-02 15:49:20 -07002444 }
2445 });
2446
2447 Trace.endSection();
2448 Trace.beginSection("contactInteractionsToEntries");
2449
2450 // Wrap each interaction in its own list so that an icon is displayed for each entry
2451 for (Entry contactInteraction : contactInteractionsToEntries(allInteractions)) {
2452 List<Entry> entryListWrapper = new ArrayList<>(1);
2453 entryListWrapper.add(contactInteraction);
2454 interactionsWrapper.add(entryListWrapper);
2455 }
2456
2457 Trace.endSection();
2458 return null;
2459 }
2460
2461 @Override
2462 protected void onPostExecute(Void aVoid) {
2463 super.onPostExecute(aVoid);
2464 Trace.beginSection("initialize recents card");
2465
2466 if (allInteractions.size() > 0) {
2467 mRecentCard.initialize(interactionsWrapper,
Paul Soulosb3054e52014-06-05 16:46:02 -07002468 /* numInitialVisibleEntries = */ MIN_NUM_COLLAPSED_RECENT_ENTRIES_SHOWN,
Paul Soulosc8e2a912014-07-21 14:52:20 -07002469 /* isExpanded = */ mRecentCard.isExpanded(), /* isAlwaysExpanded = */ false,
Brian Attwell30e1ef12014-09-02 15:49:20 -07002470 mExpandingEntryCardViewListener, mScroller);
yaolu79525d02016-08-24 12:08:39 -07002471 if (mRecentCard.getVisibility() == View.GONE && mShouldLog) {
2472 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.RECENT,
2473 ActionType.UNKNOWN_ACTION, /* thirdPartyAction */ null);
2474 }
Brian Attwell30e1ef12014-09-02 15:49:20 -07002475 mRecentCard.setVisibility(View.VISIBLE);
yaolu38fa2852016-11-10 13:08:58 -08002476 } else {
2477 mRecentCard.setVisibility(View.GONE);
Brian Attwell30e1ef12014-09-02 15:49:20 -07002478 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07002479
Brian Attwell30e1ef12014-09-02 15:49:20 -07002480 Trace.endSection();
guanxiongliu04a19362016-05-18 14:38:51 -07002481 Trace.beginSection("initialize permission explanation card");
2482
guanxiongliu8e56edf2016-07-14 13:48:55 -07002483 final Drawable historyIcon = VectorDrawableCompat.create(
2484 getResources(), R.drawable.ic_history_24dp, null);
2485
guanxiongliu04a19362016-05-18 14:38:51 -07002486 final Entry permissionExplanationEntry = new Entry(CARD_ENTRY_ID_REQUEST_PERMISSION,
2487 historyIcon, getString(R.string.permission_explanation_header),
2488 mPermissionExplanationCardSubHeader, /* subHeaderIcon = */ null,
2489 /* text = */ null, /* textIcon = */ null,
2490 /* primaryContentDescription = */ null, getIntent(),
2491 /* alternateIcon = */ null, /* alternateIntent = */ null,
2492 /* alternateContentDescription = */ null, /* shouldApplyColor = */ true,
2493 /* isEditable = */ false, /* EntryContextMenuInfo = */ null,
2494 /* thirdIcon = */ null, /* thirdIntent = */ null,
2495 /* thirdContentDescription = */ null, /* thirdAction = */ Entry.ACTION_NONE,
2496 /* thirdExtras = */ null, R.drawable.ic_history_24dp);
2497
2498 final List<List<Entry>> permissionExplanationEntries = new ArrayList<>();
2499 permissionExplanationEntries.add(new ArrayList<Entry>());
2500 permissionExplanationEntries.get(0).add(permissionExplanationEntry);
2501
2502 final int subHeaderTextColor = getResources().getColor(android.R.color.white);
2503 final PorterDuffColorFilter whiteColorFilter =
2504 new PorterDuffColorFilter(subHeaderTextColor, PorterDuff.Mode.SRC_ATOP);
2505
2506 mPermissionExplanationCard.initialize(permissionExplanationEntries,
2507 /* numInitialVisibleEntries = */ 1,
2508 /* isExpanded = */ true,
2509 /* isAlwaysExpanded = */ true,
2510 /* listener = */ null,
2511 mScroller);
2512
2513 mPermissionExplanationCard.setColorAndFilter(subHeaderTextColor, whiteColorFilter);
2514 mPermissionExplanationCard.setBackgroundColor(mColorFilterColor);
2515 mPermissionExplanationCard.setEntryHeaderColor(subHeaderTextColor);
2516 mPermissionExplanationCard.setEntrySubHeaderColor(subHeaderTextColor);
2517
2518 if (mShouldShowPermissionExplanation) {
yaolu79525d02016-08-24 12:08:39 -07002519 if (mPermissionExplanationCard.getVisibility() == View.GONE
2520 && mShouldLog) {
2521 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.PERMISSION,
2522 ActionType.UNKNOWN_ACTION, /* thirdPartyAction */ null);
2523 }
guanxiongliu04a19362016-05-18 14:38:51 -07002524 mPermissionExplanationCard.setVisibility(View.VISIBLE);
2525 } else {
2526 mPermissionExplanationCard.setVisibility(View.GONE);
2527 }
2528
2529 Trace.endSection();
Brian Attwell30e1ef12014-09-02 15:49:20 -07002530
2531 // About card is initialized along with the contact card, but since it appears after
2532 // the recent card in the UI, we hold off until making it visible until the recent
2533 // card is also ready to avoid stuttering.
2534 if (mAboutCard.shouldShow()) {
2535 mAboutCard.setVisibility(View.VISIBLE);
2536 } else {
2537 mAboutCard.setVisibility(View.GONE);
2538 }
2539 mRecentDataTask = null;
2540 }
2541 };
2542 mRecentDataTask.execute();
Paul Soulosb3054e52014-06-05 16:46:02 -07002543 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002544
2545 @Override
2546 protected void onStop() {
2547 super.onStop();
2548
2549 if (mEntriesAndActionsTask != null) {
2550 // Once the activity is stopped, we will no longer want to bind mEntriesAndActionsTask's
2551 // results on the UI thread. In some circumstances Activities are killed without
2552 // onStop() being called. This is not a problem, because in these circumstances
2553 // the entire process will be killed.
2554 mEntriesAndActionsTask.cancel(/* mayInterruptIfRunning = */ false);
2555 }
Brian Attwell30e1ef12014-09-02 15:49:20 -07002556 if (mRecentDataTask != null) {
2557 mRecentDataTask.cancel(/* mayInterruptIfRunning = */ false);
2558 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002559 }
Paul Soulos23889ba2014-06-10 14:55:32 -07002560
Tingting Wange3a7c4f2015-09-24 18:23:18 -07002561 @Override
2562 public void onDestroy() {
Wenyi Wang1e5b77f2016-11-29 18:07:13 -08002563 LocalBroadcastManager.getInstance(this).unregisterReceiver(mListener);
Tingting Wange3a7c4f2015-09-24 18:23:18 -07002564 super.onDestroy();
Tingting Wange3a7c4f2015-09-24 18:23:18 -07002565 }
2566
Paul Soulos23889ba2014-06-10 14:55:32 -07002567 /**
Brian Attwelld28851f2014-06-10 13:25:07 -07002568 * Returns true if it is possible to edit the current contact.
2569 */
2570 private boolean isContactEditable() {
2571 return mContactData != null && !mContactData.isDirectoryEntry();
2572 }
2573
Brian Attwella41d6d12014-07-31 15:23:13 -07002574 /**
2575 * Returns true if it is possible to share the current contact.
2576 */
2577 private boolean isContactShareable() {
2578 return mContactData != null && !mContactData.isDirectoryEntry();
2579 }
2580
Brian Attwell60953692014-07-11 17:18:46 -07002581 private Intent getEditContactIntent() {
Gary Mai363af602016-09-28 10:01:23 -07002582 return EditorIntents.createEditContactIntent(QuickContactActivity.this,
Walter Jang1e8801b2015-03-10 15:57:05 -07002583 mContactData.getLookupUri(),
2584 mHasComputedThemeColor
2585 ? new MaterialPalette(mColorFilterColor, mStatusBarColor) : null,
Wenyi Wangc41a1e52016-01-29 17:01:35 +00002586 mContactData.getPhotoId());
Brian Attwell60953692014-07-11 17:18:46 -07002587 }
2588
2589 private void editContact() {
Paul Soulosa42ef762014-08-20 10:26:10 -07002590 mHasIntentLaunched = true;
Zheng Fuc00a0b52014-09-02 16:44:44 -07002591 mContactLoader.cacheResult();
Brian Attwell60953692014-07-11 17:18:46 -07002592 startActivityForResult(getEditContactIntent(), REQUEST_CODE_CONTACT_EDITOR_ACTIVITY);
Brian Attwelld28851f2014-06-10 13:25:07 -07002593 }
2594
Walter Jang5d2e2622014-11-03 13:20:48 -08002595 private void deleteContact() {
2596 final Uri contactUri = mContactData.getLookupUri();
2597 ContactDeletionInteraction.start(this, contactUri, /* finishActivityWhenDone =*/ true);
2598 }
2599
yaolu79525d02016-08-24 12:08:39 -07002600 private void toggleStar(MenuItem starredMenuItem, boolean isStarred) {
2601 // To improve responsiveness, swap out the picture (and tag) in the UI already
2602 ContactDisplayUtils.configureStarredMenuItem(starredMenuItem,
2603 mContactData.isDirectoryEntry(), mContactData.isUserProfile(), !isStarred);
Brian Attwelld28851f2014-06-10 13:25:07 -07002604
yaolu79525d02016-08-24 12:08:39 -07002605 // Now perform the real save
2606 final Intent intent = ContactSaveService.createSetStarredIntent(
2607 QuickContactActivity.this, mContactData.getLookupUri(), !isStarred);
2608 startService(intent);
Brian Attwelld28851f2014-06-10 13:25:07 -07002609
yaolu79525d02016-08-24 12:08:39 -07002610 final CharSequence accessibilityText = !isStarred
2611 ? getResources().getText(R.string.description_action_menu_add_star)
2612 : getResources().getText(R.string.description_action_menu_remove_star);
2613 // Accessibility actions need to have an associated view. We can't access the MenuItem's
2614 // underlying view, so put this accessibility action on the root view.
2615 mScroller.announceForAccessibility(accessibilityText);
Brian Attwelld28851f2014-06-10 13:25:07 -07002616 }
2617
Brian Attwell752cccf2014-06-10 16:24:04 -07002618 private void shareContact() {
2619 final String lookupKey = mContactData.getLookupKey();
Brian Attwell652936f2015-05-27 19:40:36 -07002620 final Uri shareUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey);
Brian Attwell752cccf2014-06-10 16:24:04 -07002621 final Intent intent = new Intent(Intent.ACTION_SEND);
Yorke Lee552ee562015-07-28 15:58:56 -07002622 intent.setType(Contacts.CONTENT_VCARD_TYPE);
Brian Attwell8dcb4ed2015-06-15 15:45:03 -07002623 intent.putExtra(Intent.EXTRA_STREAM, shareUri);
Brian Attwell752cccf2014-06-10 16:24:04 -07002624
2625 // Launch chooser to share contact via
Wenyi Wang281689f2016-05-30 10:32:30 -07002626 final CharSequence chooseTitle = getResources().getQuantityString(
2627 R.plurals.title_share_via, /* quantity */ 1);
Brian Attwell752cccf2014-06-10 16:24:04 -07002628 final Intent chooseIntent = Intent.createChooser(intent, chooseTitle);
2629
2630 try {
Paul Soulosa42ef762014-08-20 10:26:10 -07002631 mHasIntentLaunched = true;
Brian Attwell652936f2015-05-27 19:40:36 -07002632 ImplicitIntentsUtil.startActivityOutsideApp(this, chooseIntent);
Paul Souloseb64a4b2014-07-07 17:03:27 -07002633 } catch (final ActivityNotFoundException ex) {
Brian Attwell752cccf2014-06-10 16:24:04 -07002634 Toast.makeText(this, R.string.share_error, Toast.LENGTH_SHORT).show();
2635 }
2636 }
2637
2638 /**
2639 * Creates a launcher shortcut with the current contact.
2640 */
2641 private void createLauncherShortcutWithContact() {
2642 final ShortcutIntentBuilder builder = new ShortcutIntentBuilder(this,
2643 new OnShortcutIntentCreatedListener() {
2644
2645 @Override
2646 public void onShortcutIntentCreated(Uri uri, Intent shortcutIntent) {
2647 // Broadcast the shortcutIntent to the launcher to create a
2648 // shortcut to this contact
2649 shortcutIntent.setAction(ACTION_INSTALL_SHORTCUT);
2650 QuickContactActivity.this.sendBroadcast(shortcutIntent);
2651
2652 // Send a toast to give feedback to the user that a shortcut to this
2653 // contact was added to the launcher.
guanxiongliuce8fc7b2016-03-28 11:32:32 -07002654 final String displayName = shortcutIntent
2655 .getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
Tingting Wang168331d2015-10-30 12:00:57 -07002656 final String toastMessage = TextUtils.isEmpty(displayName)
2657 ? getString(R.string.createContactShortcutSuccessful_NoName)
2658 : getString(R.string.createContactShortcutSuccessful, displayName);
2659 Toast.makeText(QuickContactActivity.this, toastMessage,
Brian Attwell752cccf2014-06-10 16:24:04 -07002660 Toast.LENGTH_SHORT).show();
2661 }
2662
2663 });
Brian Attwell63176c92014-08-18 15:14:18 -07002664 builder.createContactShortcutIntent(mContactData.getLookupUri());
Brian Attwell752cccf2014-06-10 16:24:04 -07002665 }
2666
Brian Attwell66965e12014-09-08 16:15:20 -07002667 private boolean isShortcutCreatable() {
Jay Shrauner1c06ce72015-05-01 11:56:08 -07002668 if (mContactData == null || mContactData.isUserProfile() ||
2669 mContactData.isDirectoryEntry()) {
Brian Attwell8d0557e2014-12-02 11:18:16 -08002670 return false;
2671 }
Brian Attwell66965e12014-09-08 16:15:20 -07002672 final Intent createShortcutIntent = new Intent();
2673 createShortcutIntent.setAction(ACTION_INSTALL_SHORTCUT);
2674 final List<ResolveInfo> receivers = getPackageManager()
2675 .queryBroadcastReceivers(createShortcutIntent, 0);
2676 return receivers != null && receivers.size() > 0;
2677 }
2678
yaolu58a1ac22016-10-24 16:50:24 -07002679 private void setStateForPhoneMenuItems(Contact contact) {
2680 if (contact != null) {
2681 mSendToVoicemailState = contact.isSendToVoicemail();
2682 mCustomRingtone = contact.getCustomRingtone();
2683 mArePhoneOptionsChangable = isContactEditable()
2684 && PhoneCapabilityTester.isPhone(this);
2685 }
2686 }
2687
Brian Attwelld28851f2014-06-10 13:25:07 -07002688 @Override
2689 public boolean onCreateOptionsMenu(Menu menu) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07002690 final MenuInflater inflater = getMenuInflater();
Brian Attwelld28851f2014-06-10 13:25:07 -07002691 inflater.inflate(R.menu.quickcontact, menu);
2692 return true;
2693 }
2694
2695 @Override
2696 public boolean onPrepareOptionsMenu(Menu menu) {
2697 if (mContactData != null) {
2698 final MenuItem starredMenuItem = menu.findItem(R.id.menu_star);
Paul Soulos333091a2014-07-22 13:54:41 -07002699 ContactDisplayUtils.configureStarredMenuItem(starredMenuItem,
Brian Attwelld28851f2014-06-10 13:25:07 -07002700 mContactData.isDirectoryEntry(), mContactData.isUserProfile(),
2701 mContactData.getStarred());
Brian Attwella41d6d12014-07-31 15:23:13 -07002702
Brian Attwelld28851f2014-06-10 13:25:07 -07002703 // Configure edit MenuItem
2704 final MenuItem editMenuItem = menu.findItem(R.id.menu_edit);
2705 editMenuItem.setVisible(true);
2706 if (DirectoryContactUtil.isDirectoryContact(mContactData) || InvisibleContactUtil
2707 .isInvisibleAndAddable(mContactData, this)) {
Brian Attwell30cfd122014-06-13 16:42:11 -07002708 editMenuItem.setIcon(R.drawable.ic_person_add_tinted_24dp);
Brian Attwell2e4214c2014-07-10 22:03:16 -07002709 editMenuItem.setTitle(R.string.menu_add_contact);
Brian Attwelld28851f2014-06-10 13:25:07 -07002710 } else if (isContactEditable()) {
2711 editMenuItem.setIcon(R.drawable.ic_create_24dp);
Brian Attwell2e4214c2014-07-10 22:03:16 -07002712 editMenuItem.setTitle(R.string.menu_editContact);
Brian Attwelld28851f2014-06-10 13:25:07 -07002713 } else {
2714 editMenuItem.setVisible(false);
2715 }
Brian Attwella41d6d12014-07-31 15:23:13 -07002716
Gary Maib9065dd2016-11-08 10:49:00 -08002717 // The link menu item is only visible if this has a single raw contact.
yaolu2de7c8e2016-09-26 09:45:44 -07002718 final MenuItem joinMenuItem = menu.findItem(R.id.menu_join);
2719 joinMenuItem.setVisible(!InvisibleContactUtil.isInvisibleAndAddable(mContactData, this)
Gary Maib9065dd2016-11-08 10:49:00 -08002720 && isContactEditable() && !mContactData.isUserProfile()
2721 && !mContactData.isMultipleRawContacts());
2722
2723 // Viewing linked contacts can only happen if there are multiple raw contacts and
2724 // the link menu isn't available.
2725 final MenuItem linkedContactsMenuItem = menu.findItem(R.id.menu_linked_contacts);
2726 linkedContactsMenuItem.setVisible(mContactData.isMultipleRawContacts()
2727 && !joinMenuItem.isVisible());
yaolu2de7c8e2016-09-26 09:45:44 -07002728
Walter Jang5d2e2622014-11-03 13:20:48 -08002729 final MenuItem deleteMenuItem = menu.findItem(R.id.menu_delete);
Walter Jang82ed2b52015-09-09 12:01:04 -07002730 deleteMenuItem.setVisible(isContactEditable() && !mContactData.isUserProfile());
Walter Jang5d2e2622014-11-03 13:20:48 -08002731
Brian Attwella41d6d12014-07-31 15:23:13 -07002732 final MenuItem shareMenuItem = menu.findItem(R.id.menu_share);
2733 shareMenuItem.setVisible(isContactShareable());
2734
Brian Attwell66965e12014-09-08 16:15:20 -07002735 final MenuItem shortcutMenuItem = menu.findItem(R.id.menu_create_contact_shortcut);
2736 shortcutMenuItem.setVisible(isShortcutCreatable());
2737
yaolu58a1ac22016-10-24 16:50:24 -07002738 // Hide telephony-related settings (ringtone, send to voicemail)
2739 // if we don't have a telephone
2740 final MenuItem ringToneMenuItem = menu.findItem(R.id.menu_set_ringtone);
2741 ringToneMenuItem.setVisible(!mContactData.isUserProfile() && mArePhoneOptionsChangable);
2742
2743 final MenuItem sendToVoiceMailMenuItem = menu.findItem(R.id.menu_send_to_voicemail);
2744 sendToVoiceMailMenuItem.setVisible(!mContactData.isUserProfile()
2745 && mArePhoneOptionsChangable);
2746 sendToVoiceMailMenuItem.setTitle(mSendToVoicemailState
2747 ? R.string.menu_unredirect_calls_to_vm : R.string.menu_redirect_calls_to_vm);
2748
Brian Attwell56bcc2f2015-02-12 23:45:27 +00002749 final MenuItem helpMenu = menu.findItem(R.id.menu_help);
2750 helpMenu.setVisible(HelpUtils.isHelpAndFeedbackAvailable());
2751
Paul Soulos8a6d0022014-06-16 15:11:03 -07002752 return true;
Brian Attwelld28851f2014-06-10 13:25:07 -07002753 }
Paul Soulos8a6d0022014-06-16 15:11:03 -07002754 return false;
Brian Attwelld28851f2014-06-10 13:25:07 -07002755 }
2756
2757 @Override
2758 public boolean onOptionsItemSelected(MenuItem item) {
2759 switch (item.getItemId()) {
2760 case R.id.menu_star:
yaolu79525d02016-08-24 12:08:39 -07002761 // Make sure there is a contact
2762 if (mContactData != null) {
2763 // Read the current starred value from the UI instead of using the last
2764 // loaded state. This allows rapid tapping without writing the same
2765 // value several times
2766 final boolean isStarred = item.isChecked();
2767 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
2768 isStarred ? ActionType.UNSTAR : ActionType.STAR,
2769 /* thirdPartyAction */ null);
2770 toggleStar(item, isStarred);
2771 }
Brian Attwelld28851f2014-06-10 13:25:07 -07002772 return true;
2773 case R.id.menu_edit:
2774 if (DirectoryContactUtil.isDirectoryContact(mContactData)) {
yaolu79525d02016-08-24 12:08:39 -07002775 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
2776 ActionType.ADD, /* thirdPartyAction */ null);
2777
Paul Soulosab54ea12014-08-28 17:22:38 -07002778 // This action is used to launch the contact selector, with the option of
2779 // creating a new contact. Creating a new contact is an INSERT, while selecting
2780 // an exisiting one is an edit. The fields in the edit screen will be
2781 // prepopulated with data.
2782
2783 final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
2784 intent.setType(Contacts.CONTENT_ITEM_TYPE);
2785
Paul Soulosab54ea12014-08-28 17:22:38 -07002786 ArrayList<ContentValues> values = mContactData.getContentValues();
Brian Attwellfc423b42014-10-17 12:58:15 -07002787
2788 // Only pre-fill the name field if the provided display name is an nickname
2789 // or better (e.g. structured name, nickname)
2790 if (mContactData.getDisplayNameSource() >= DisplayNameSources.NICKNAME) {
2791 intent.putExtra(Intents.Insert.NAME, mContactData.getDisplayName());
2792 } else if (mContactData.getDisplayNameSource()
2793 == DisplayNameSources.ORGANIZATION) {
2794 // This is probably an organization. Instead of copying the organization
2795 // name into a name entry, copy it into the organization entry. This
2796 // way we will still consider the contact an organization.
2797 final ContentValues organization = new ContentValues();
2798 organization.put(Organization.COMPANY, mContactData.getDisplayName());
2799 organization.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
2800 values.add(organization);
2801 }
2802
Paul Soulosab54ea12014-08-28 17:22:38 -07002803 // Last time used and times used are aggregated values from the usage stat
2804 // table. They need to be removed from data values so the SQL table can insert
2805 // properly
2806 for (ContentValues value : values) {
2807 value.remove(Data.LAST_TIME_USED);
2808 value.remove(Data.TIMES_USED);
2809 }
2810 intent.putExtra(Intents.Insert.DATA, values);
2811
2812 // If the contact can only export to the same account, add it to the intent.
Gary Mai363af602016-09-28 10:01:23 -07002813 // Otherwise the ContactEditorFragment will show a dialog for selecting
Walter Jang7b0970f2016-09-01 10:40:19 -07002814 // an account.
Paul Soulosab54ea12014-08-28 17:22:38 -07002815 if (mContactData.getDirectoryExportSupport() ==
2816 Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY) {
Brian Attwell4a1c5742015-01-26 15:59:58 -08002817 intent.putExtra(Intents.Insert.EXTRA_ACCOUNT,
Paul Soulosab54ea12014-08-28 17:22:38 -07002818 new Account(mContactData.getDirectoryAccountName(),
2819 mContactData.getDirectoryAccountType()));
Brian Attwell4a1c5742015-01-26 15:59:58 -08002820 intent.putExtra(Intents.Insert.EXTRA_DATA_SET,
Paul Soulosab54ea12014-08-28 17:22:38 -07002821 mContactData.getRawContacts().get(0).getDataSet());
2822 }
2823
Paul Soulosf19dda92014-09-15 15:48:02 -07002824 // Add this flag to disable the delete menu option on directory contact joins
2825 // with local contacts. The delete option is ambiguous when joining contacts.
Walter Jang8bac28b2016-08-30 10:34:55 -07002826 intent.putExtra(
Gary Mai363af602016-09-28 10:01:23 -07002827 ContactEditorFragment.INTENT_EXTRA_DISABLE_DELETE_MENU_OPTION,
Paul Soulosf19dda92014-09-15 15:48:02 -07002828 true);
2829
John Shao41c68862016-08-17 21:02:41 -07002830 intent.setPackage(getPackageName());
Paul Soulosab54ea12014-08-28 17:22:38 -07002831 startActivityForResult(intent, REQUEST_CODE_CONTACT_SELECTION_ACTIVITY);
Brian Attwelld28851f2014-06-10 13:25:07 -07002832 } else if (InvisibleContactUtil.isInvisibleAndAddable(mContactData, this)) {
yaolu79525d02016-08-24 12:08:39 -07002833 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
2834 ActionType.ADD, /* thirdPartyAction */ null);
Brian Attwelld28851f2014-06-10 13:25:07 -07002835 InvisibleContactUtil.addToDefaultGroup(mContactData, this);
2836 } else if (isContactEditable()) {
yaolu79525d02016-08-24 12:08:39 -07002837 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
2838 ActionType.EDIT, /* thirdPartyAction */ null);
Brian Attwelld28851f2014-06-10 13:25:07 -07002839 editContact();
2840 }
2841 return true;
yaolu2de7c8e2016-09-26 09:45:44 -07002842 case R.id.menu_join:
2843 return doJoinContactAction();
Gary Maib9065dd2016-11-08 10:49:00 -08002844 case R.id.menu_linked_contacts:
2845 return showRawContactPickerDialog();
Walter Jang5d2e2622014-11-03 13:20:48 -08002846 case R.id.menu_delete:
yaolu79525d02016-08-24 12:08:39 -07002847 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
yaolu7a09c702016-09-01 18:12:03 -07002848 ActionType.REMOVE, /* thirdPartyAction */ null);
Jay Shrauner1c06ce72015-05-01 11:56:08 -07002849 if (isContactEditable()) {
2850 deleteContact();
2851 }
Walter Jang5d2e2622014-11-03 13:20:48 -08002852 return true;
Brian Attwell752cccf2014-06-10 16:24:04 -07002853 case R.id.menu_share:
yaolu79525d02016-08-24 12:08:39 -07002854 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
2855 ActionType.SHARE, /* thirdPartyAction */ null);
Jay Shrauner49de62f2014-11-26 10:44:28 -08002856 if (isContactShareable()) {
2857 shareContact();
2858 }
Brian Attwell752cccf2014-06-10 16:24:04 -07002859 return true;
2860 case R.id.menu_create_contact_shortcut:
yaolu79525d02016-08-24 12:08:39 -07002861 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
2862 ActionType.SHORTCUT, /* thirdPartyAction */ null);
Jay Shrauner1c06ce72015-05-01 11:56:08 -07002863 if (isShortcutCreatable()) {
2864 createLauncherShortcutWithContact();
2865 }
Brian Attwell752cccf2014-06-10 16:24:04 -07002866 return true;
yaolu58a1ac22016-10-24 16:50:24 -07002867 case R.id.menu_set_ringtone:
2868 doPickRingtone();
2869 return true;
2870 case R.id.menu_send_to_voicemail:
2871 // Update state and save
2872 mSendToVoicemailState = !mSendToVoicemailState;
2873 item.setTitle(mSendToVoicemailState
2874 ? R.string.menu_unredirect_calls_to_vm
2875 : R.string.menu_redirect_calls_to_vm);
2876 final Intent intent = ContactSaveService.createSetSendToVoicemail(
2877 this, mLookupUri, mSendToVoicemailState);
2878 this.startService(intent);
2879 return true;
Brian Attwell56bcc2f2015-02-12 23:45:27 +00002880 case R.id.menu_help:
yaolu79525d02016-08-24 12:08:39 -07002881 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
2882 ActionType.HELP, /* thirdPartyAction */ null);
Brian Attwell56bcc2f2015-02-12 23:45:27 +00002883 HelpUtils.launchHelpAndFeedbackForContactScreen(this);
2884 return true;
Brian Attwelld28851f2014-06-10 13:25:07 -07002885 default:
yaolu79525d02016-08-24 12:08:39 -07002886 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
2887 ActionType.UNKNOWN_ACTION, /* thirdPartyAction */ null);
Brian Attwelld28851f2014-06-10 13:25:07 -07002888 return super.onOptionsItemSelected(item);
2889 }
Paul Soulos23889ba2014-06-10 14:55:32 -07002890 }
yaolu2de7c8e2016-09-26 09:45:44 -07002891
Gary Maib9065dd2016-11-08 10:49:00 -08002892 private boolean showRawContactPickerDialog() {
2893 if (mContactData == null) return false;
2894 startActivityForResult(EditorIntents.createViewLinkedContactsIntent(
2895 QuickContactActivity.this,
2896 mContactData.getLookupUri(),
2897 mHasComputedThemeColor
2898 ? new MaterialPalette(mColorFilterColor, mStatusBarColor)
2899 : null),
2900 REQUEST_CODE_CONTACT_EDITOR_ACTIVITY);
2901 return true;
2902 }
2903
yaolu2de7c8e2016-09-26 09:45:44 -07002904 private boolean doJoinContactAction() {
2905 if (mContactData == null) return false;
2906
2907 mPreviousContactId = mContactData.getId();
2908 final Intent intent = new Intent(this, ContactSelectionActivity.class);
2909 intent.setAction(UiIntentActions.PICK_JOIN_CONTACT_ACTION);
2910 intent.putExtra(UiIntentActions.TARGET_CONTACT_ID_EXTRA_KEY, mPreviousContactId);
2911 startActivityForResult(intent, REQUEST_CODE_JOIN);
2912 return true;
2913 }
2914
2915 /**
2916 * Performs aggregation with the contact selected by the user from suggestions or A-Z list.
2917 */
2918 private void joinAggregate(final long contactId) {
2919 final Intent intent = ContactSaveService.createJoinContactsIntent(
2920 this, mPreviousContactId, contactId, QuickContactActivity.class,
2921 Intent.ACTION_VIEW);
2922 this.startService(intent);
Gary Maib9065dd2016-11-08 10:49:00 -08002923 showLinkProgressBar();
yaolu2de7c8e2016-09-26 09:45:44 -07002924 }
2925
yaolu58a1ac22016-10-24 16:50:24 -07002926
2927 private void doPickRingtone() {
2928 final Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
2929 // Allow user to pick 'Default'
2930 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
2931 // Show only ringtones
2932 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE);
2933 // Allow the user to pick a silent ringtone
2934 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
2935
2936 final Uri ringtoneUri = EditorUiUtils.getRingtoneUriFromString(mCustomRingtone,
2937 CURRENT_API_VERSION);
2938
2939 // Put checkmark next to the current ringtone for this contact
2940 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, ringtoneUri);
2941
2942 // Launch!
2943 try {
2944 startActivityForResult(intent, REQUEST_CODE_PICK_RINGTONE);
2945 } catch (ActivityNotFoundException ex) {
2946 Toast.makeText(this, R.string.missing_app, Toast.LENGTH_SHORT).show();
2947 }
2948 }
Gary Maib9065dd2016-11-08 10:49:00 -08002949
2950 private void dismissProgressBar() {
2951 if (mProgressDialog != null && mProgressDialog.isShowing()) {
2952 mProgressDialog.dismiss();
2953 }
2954 }
2955
2956 private void showLinkProgressBar() {
2957 mProgressDialog.setMessage(getString(R.string.contacts_linking_progress_bar));
2958 mProgressDialog.show();
2959 }
2960
2961 private void showUnlinkProgressBar() {
2962 mProgressDialog.setMessage(getString(R.string.contacts_unlinking_progress_bar));
2963 mProgressDialog.show();
2964 }
2965
2966 private void maybeShowProgressDialog() {
2967 if (ContactSaveService.getState().isActionPending(
2968 ContactSaveService.ACTION_SPLIT_CONTACT)) {
2969 showUnlinkProgressBar();
2970 } else if (ContactSaveService.getState().isActionPending(
2971 ContactSaveService.ACTION_JOIN_CONTACTS)) {
2972 showLinkProgressBar();
2973 }
2974 }
2975
2976 private class SaveServiceListener extends BroadcastReceiver {
2977 @Override
2978 public void onReceive(Context context, Intent intent) {
2979 if (Log.isLoggable(TAG, Log.DEBUG)) {
2980 Log.d(TAG, "Got broadcast from save service " + intent);
2981 }
2982 if (ContactSaveService.BROADCAST_LINK_COMPLETE.equals(intent.getAction())
2983 || ContactSaveService.BROADCAST_UNLINK_COMPLETE.equals(intent.getAction())) {
2984 dismissProgressBar();
2985 if (ContactSaveService.BROADCAST_UNLINK_COMPLETE.equals(intent.getAction())) {
2986 finish();
2987 }
2988 }
2989 }
2990 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -07002991}