blob: 3c04f68663dffaf1dbc6fe7a76b2e83d87ae6c97 [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
Gary Mai0a49afa2016-12-05 15:53:58 -080099import com.android.contacts.CallUtil;
100import com.android.contacts.ClipboardUtils;
101import com.android.contacts.Collapser;
Christine Chen72b3ab12013-08-13 23:22:34 +0200102import com.android.contacts.ContactSaveService;
Brian Attwelld28851f2014-06-10 13:25:07 -0700103import com.android.contacts.ContactsActivity;
Gary Mai0a49afa2016-12-05 15:53:58 -0800104import com.android.contacts.ContactsUtils;
Marcus Hagerottd105c1e2016-09-30 14:28:00 -0700105import com.android.contacts.DynamicShortcuts;
Brian Attwell2d150da2014-07-09 22:35:56 -0700106import com.android.contacts.NfcHandler;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -0700107import com.android.contacts.R;
Gary Mai363af602016-09-28 10:01:23 -0700108import com.android.contacts.activities.ContactEditorActivity;
yaolu2de7c8e2016-09-26 09:45:44 -0700109import com.android.contacts.activities.ContactSelectionActivity;
Gary Mai0a49afa2016-12-05 15:53:58 -0800110import com.android.contacts.activities.RequestDesiredPermissionsActivity;
111import com.android.contacts.activities.RequestPermissionsActivity;
Gary Mai69c182a2016-12-05 13:07:03 -0800112import com.android.contacts.compat.CompatUtils;
113import com.android.contacts.compat.EventCompat;
114import com.android.contacts.compat.MultiWindowCompat;
Gary Mai0a49afa2016-12-05 15:53:58 -0800115import com.android.contacts.detail.ContactDisplayUtils;
Gary Mai69c182a2016-12-05 13:07:03 -0800116import com.android.contacts.dialog.CallSubjectDialog;
Gary Mai0a49afa2016-12-05 15:53:58 -0800117import com.android.contacts.editor.ContactEditorFragment;
118import com.android.contacts.editor.EditorIntents;
119import com.android.contacts.editor.EditorUiUtils;
120import com.android.contacts.interactions.CalendarInteractionsLoader;
121import com.android.contacts.interactions.CallLogInteractionsLoader;
122import com.android.contacts.interactions.ContactDeletionInteraction;
123import com.android.contacts.interactions.ContactInteraction;
124import com.android.contacts.interactions.SmsInteractionsLoader;
Gary Mai69c182a2016-12-05 13:07:03 -0800125import com.android.contacts.interactions.TouchPointManager;
126import com.android.contacts.lettertiles.LetterTileDrawable;
127import com.android.contacts.list.ShortcutIntentBuilder;
128import com.android.contacts.list.ShortcutIntentBuilder.OnShortcutIntentCreatedListener;
Gary Mai0a49afa2016-12-05 15:53:58 -0800129import com.android.contacts.list.UiIntentActions;
Gary Mai69c182a2016-12-05 13:07:03 -0800130import com.android.contacts.logging.Logger;
131import com.android.contacts.logging.QuickContactEvent.ActionType;
132import com.android.contacts.logging.QuickContactEvent.CardType;
133import com.android.contacts.logging.QuickContactEvent.ContactType;
134import com.android.contacts.logging.ScreenEvent.ScreenType;
135import com.android.contacts.model.AccountTypeManager;
136import com.android.contacts.model.Contact;
137import com.android.contacts.model.ContactLoader;
138import com.android.contacts.model.RawContact;
139import com.android.contacts.model.account.AccountType;
140import com.android.contacts.model.dataitem.CustomDataItem;
141import com.android.contacts.model.dataitem.DataItem;
142import com.android.contacts.model.dataitem.DataKind;
143import com.android.contacts.model.dataitem.EmailDataItem;
144import com.android.contacts.model.dataitem.EventDataItem;
145import com.android.contacts.model.dataitem.ImDataItem;
146import com.android.contacts.model.dataitem.NicknameDataItem;
147import com.android.contacts.model.dataitem.NoteDataItem;
148import com.android.contacts.model.dataitem.OrganizationDataItem;
149import com.android.contacts.model.dataitem.PhoneDataItem;
150import com.android.contacts.model.dataitem.RelationDataItem;
151import com.android.contacts.model.dataitem.SipAddressDataItem;
152import com.android.contacts.model.dataitem.StructuredNameDataItem;
153import com.android.contacts.model.dataitem.StructuredPostalDataItem;
154import com.android.contacts.model.dataitem.WebsiteDataItem;
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700155import com.android.contacts.quickcontact.ExpandingEntryCardView.Entry;
Paul Soulos2a4207f2014-07-31 17:09:05 -0700156import com.android.contacts.quickcontact.ExpandingEntryCardView.EntryContextMenuInfo;
Paul Soulos2ed2a732014-08-12 11:58:39 -0700157import com.android.contacts.quickcontact.ExpandingEntryCardView.EntryTag;
Brian Attwelle8ce6ee2014-06-27 18:26:32 -0700158import com.android.contacts.quickcontact.ExpandingEntryCardView.ExpandingEntryCardViewListener;
Brian Attwell493f8d12014-12-18 12:42:03 -0800159import com.android.contacts.quickcontact.WebAddress.ParseException;
Gary Mai0a49afa2016-12-05 15:53:58 -0800160import com.android.contacts.util.DateUtils;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -0700161import com.android.contacts.util.ImageViewDrawableSetter;
Gary Mai0a49afa2016-12-05 15:53:58 -0800162import com.android.contacts.util.ImplicitIntentsUtil;
163import com.android.contacts.util.MaterialColorMapUtils;
164import com.android.contacts.util.MaterialColorMapUtils.MaterialPalette;
165import com.android.contacts.util.PermissionsUtil;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700166import com.android.contacts.util.PhoneCapabilityTester;
Brian Attwellb7e43642014-06-02 14:33:04 -0700167import com.android.contacts.util.SchedulingUtils;
Tingting Wangac9596e2016-08-02 22:24:24 -0700168import com.android.contacts.util.SharedPreferenceUtil;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700169import com.android.contacts.util.StructuredPostalUtils;
Gary Mai0a49afa2016-12-05 15:53:58 -0800170import com.android.contacts.util.UriUtils;
171import com.android.contacts.util.ViewUtil;
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(
John Shaobd9ef3c2016-12-15 12:42:03 -08001378 R.drawable.quantum_ic_phone_vd_theme_24).mutate();
Brian Attwell60953692014-07-11 17:18:46 -07001379 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,
John Shaobd9ef3c2016-12-15 12:42:03 -08001391 R.drawable.quantum_ic_phone_vd_theme_24);
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(
John Shaobd9ef3c2016-12-15 12:42:03 -08001399 R.drawable.quantum_ic_email_vd_theme_24).mutate();
yaolu95a13452016-09-20 15:25:25 -07001400 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,
John Shaobd9ef3c2016-12-15 12:42:03 -08001410 R.drawable.quantum_ic_email_vd_theme_24);
yaolu95a13452016-09-20 15:25:25 -07001411
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);
Gary Mai69c182a2016-12-05 13:07:03 -08001726 phoneContentDescription = com.android.contacts.util.ContactDisplayUtils
Tingting Wangb6949dc2015-11-03 13:17:12 -08001727 .getTelephoneTtsSpannable(primaryContentDescription.toString(), header);
John Shaobd9ef3c2016-12-15 12:42:03 -08001728 iconResourceId = R.drawable.quantum_ic_phone_vd_theme_24;
1729 icon = res.getDrawable(iconResourceId);
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
John Shaobd9ef3c2016-12-15 12:42:03 -08001738 alternateIcon = res.getDrawable(R.drawable.quantum_ic_message_vd_theme_24);
Paul Soulos23e28362014-08-29 14:57:08 -07001739 alternateContentDescription.append(res.getString(R.string.sms_custom, header));
Gary Mai69c182a2016-12-05 13:07:03 -08001740 smsContentDescription = com.android.contacts.util.ContactDisplayUtils
Wenyi Wang5da55ff2015-11-19 13:22:40 -08001741 .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)) {
John Shaobd9ef3c2016-12-15 12:42:03 -08001749 thirdIcon = res.getDrawable(R.drawable.quantum_ic_perm_phone_msg_vd_theme_24);
Tyler Gunn5f87e922015-08-05 14:24:52 -07001750 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) {
John Shaobd9ef3c2016-12-15 12:42:03 -08001776 thirdIcon = res.getDrawable(R.drawable.quantum_ic_videocam_vd_theme_24);
Tyler Gunn0a8f9732015-12-15 15:38:54 -08001777 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);
John Shaobd9ef3c2016-12-15 12:42:03 -08001804 iconResourceId = R.drawable.quantum_ic_email_vd_theme_24;
1805 icon = res.getDrawable(iconResourceId);
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);
John Shaobd9ef3c2016-12-15 12:42:03 -08001827 alternateIcon = res.getDrawable(R.drawable.quantum_ic_directions_vd_theme_24);
Paul Soulos23e28362014-08-29 14:57:08 -07001828 alternateContentDescription.append(res.getString(
1829 R.string.content_description_directions)).append(" ").append(header);
John Shaobd9ef3c2016-12-15 12:42:03 -08001830 iconResourceId = R.drawable.quantum_ic_place_vd_theme_24;
1831 icon = res.getDrawable(iconResourceId);
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);
John Shaobd9ef3c2016-12-15 12:42:03 -08001854 iconResourceId = R.drawable.quantum_ic_dialer_sip_vd_theme_24;
1855 icon = res.getDrawable(iconResourceId);
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) {
John Shaobd9ef3c2016-12-15 12:42:03 -08001888 icon = res.getDrawable(R.drawable.quantum_ic_hangout_vd_theme_24);
1889 alternateIcon = res.getDrawable(
1890 R.drawable.quantum_ic_hangout_video_vd_theme_24);
yaolu8c3ff2c2016-08-18 20:13:40 -07001891 final HangoutsDataItemModel itemModel =
1892 new HangoutsDataItemModel(intent, alternateIntent,
1893 dataItem, secondDataItem, alternateContentDescription,
1894 header, text, context);
Paul Soulos6a2a1a72014-10-10 15:52:56 -07001895
yaolu8c3ff2c2016-08-18 20:13:40 -07001896 populateHangoutsDataItemModel(itemModel);
1897 intent = itemModel.intent;
1898 alternateIntent = itemModel.alternateIntent;
1899 alternateContentDescription = itemModel.alternateContentDescription;
1900 header = itemModel.header;
1901 text = itemModel.text;
1902 } else {
1903 if (HANGOUTS_DATA_5_VIDEO.equals(intent.getDataString())) {
John Shaobd9ef3c2016-12-15 12:42:03 -08001904 icon = res.getDrawable(R.drawable.quantum_ic_hangout_video_vd_theme_24);
Paul Soulos6a2a1a72014-10-10 15:52:56 -07001905 } else {
John Shaobd9ef3c2016-12-15 12:42:03 -08001906 icon = res.getDrawable(R.drawable.quantum_ic_hangout_vd_theme_24);
Paul Soulose0055662014-07-10 16:33:08 -07001907 }
yaolu8c3ff2c2016-08-18 20:13:40 -07001908 }
1909 } else {
1910 icon = ResolveCache.getInstance(context).getIcon(
1911 dataItem.getMimeType(), intent);
1912 // Call mutate to create a new Drawable.ConstantState for color filtering
1913 if (icon != null) {
1914 icon.mutate();
1915 }
1916 shouldApplyColor = false;
1917
1918 if (!MIMETYPE_GPLUS_PROFILE.equals(mimetype)) {
Paul Soulos97f27802014-09-08 13:55:45 -07001919 entryContextMenuInfo = new EntryContextMenuInfo(header, mimetype,
1920 dataItem.getMimeType(), dataItem.getId(),
1921 dataItem.isSuperPrimary());
yaolu8c3ff2c2016-08-18 20:13:40 -07001922 }
Paul Soulose0055662014-07-10 16:33:08 -07001923 }
1924 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001925 }
1926
Paul Souloseb64a4b2014-07-07 17:03:27 -07001927 if (intent != null) {
1928 // Do not set the intent is there are no resolves
Brian Attwell6bb01342014-08-20 23:16:00 -07001929 if (!PhoneCapabilityTester.isIntentRegistered(context, intent)) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07001930 intent = null;
Paul Soulos16339332014-06-24 13:59:56 -07001931 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001932 }
Paul Soulos16339332014-06-24 13:59:56 -07001933
Paul Soulosdd7419d2014-07-15 11:22:13 -07001934 if (alternateIntent != null) {
1935 // Do not set the alternate intent is there are no resolves
Brian Attwell6bb01342014-08-20 23:16:00 -07001936 if (!PhoneCapabilityTester.isIntentRegistered(context, alternateIntent)) {
Paul Soulosdd7419d2014-07-15 11:22:13 -07001937 alternateIntent = null;
Jay Shraunerf0d04c12014-09-02 13:44:48 -07001938 } else if (TextUtils.isEmpty(alternateContentDescription)) {
1939 // Attempt to use package manager to find a suitable content description if needed
Paul Soulos23e28362014-08-29 14:57:08 -07001940 alternateContentDescription.append(getIntentResolveLabel(alternateIntent, context));
Paul Soulosdd7419d2014-07-15 11:22:13 -07001941 }
1942 }
1943
Paul Souloseb64a4b2014-07-07 17:03:27 -07001944 // If the Entry has no visual elements, return null
1945 if (icon == null && TextUtils.isEmpty(header) && TextUtils.isEmpty(subHeader) &&
1946 subHeaderIcon == null && TextUtils.isEmpty(text) && textIcon == null) {
1947 return null;
1948 }
1949
Brian Attwellfa238942014-08-12 10:21:28 -07001950 // Ignore dataIds from the Me profile.
Paul Soulosea5e0b72014-07-08 18:09:44 -07001951 final int dataId = dataItem.getId() > Integer.MAX_VALUE ?
1952 -1 : (int) dataItem.getId();
1953
Paul Soulos23e28362014-08-29 14:57:08 -07001954 return new Entry(dataId, icon, header, subHeader, subHeaderIcon, text, textIcon,
Tingting Wangb6949dc2015-11-03 13:17:12 -08001955 phoneContentDescription == null
1956 ? new SpannableString(primaryContentDescription.toString())
1957 : phoneContentDescription,
Walter Jang7ce53522014-10-29 13:26:43 -07001958 intent, alternateIcon, alternateIntent,
Wenyi Wang5da55ff2015-11-19 13:22:40 -08001959 smsContentDescription == null
1960 ? new SpannableString(alternateContentDescription.toString())
1961 : smsContentDescription,
1962 shouldApplyColor, isEditable,
Tyler Gunn5f87e922015-08-05 14:24:52 -07001963 entryContextMenuInfo, thirdIcon, thirdIntent, thirdContentDescription, thirdAction,
1964 thirdExtras, iconResourceId);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001965 }
1966
Brian Attwell6bb01342014-08-20 23:16:00 -07001967 private List<Entry> dataItemsToEntries(List<DataItem> dataItems,
1968 MutableString aboutCardTitleOut) {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07001969 // Hangouts and G+ use two data items to create one entry.
yaolu3904fb52016-08-05 13:32:56 -07001970 if (dataItems.get(0).getMimeType().equals(MIMETYPE_GPLUS_PROFILE)) {
1971 return gPlusDataItemsToEntries(dataItems);
1972 } else if (dataItems.get(0).getMimeType().equals(MIMETYPE_HANGOUTS)) {
1973 return hangoutsDataItemsToEntries(dataItems);
Paul Soulos6a2a1a72014-10-10 15:52:56 -07001974 } else {
1975 final List<Entry> entries = new ArrayList<>();
1976 for (DataItem dataItem : dataItems) {
1977 final Entry entry = dataItemToEntry(dataItem, /* secondDataItem = */ null,
1978 this, mContactData, aboutCardTitleOut);
1979 if (entry != null) {
1980 entries.add(entry);
1981 }
1982 }
1983 return entries;
1984 }
1985 }
1986
1987 /**
yaolu3904fb52016-08-05 13:32:56 -07001988 * Put the data items into buckets based on the raw contact id
Paul Soulos6a2a1a72014-10-10 15:52:56 -07001989 */
yaolu3904fb52016-08-05 13:32:56 -07001990 private Map<Long, List<DataItem>> dataItemsToBucket(List<DataItem> dataItems) {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07001991 final Map<Long, List<DataItem>> buckets = new HashMap<>();
Paul Souloseb64a4b2014-07-07 17:03:27 -07001992 for (DataItem dataItem : dataItems) {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07001993 List<DataItem> bucket = buckets.get(dataItem.getRawContactId());
1994 if (bucket == null) {
1995 bucket = new ArrayList<>();
1996 buckets.put(dataItem.getRawContactId(), bucket);
1997 }
1998 bucket.add(dataItem);
1999 }
yaolu3904fb52016-08-05 13:32:56 -07002000 return buckets;
2001 }
2002
2003 /**
2004 * For G+ entries, a single ExpandingEntryCardView.Entry consists of two data items. This
2005 * method use only the View profile to build entry.
2006 */
2007 private List<Entry> gPlusDataItemsToEntries(List<DataItem> dataItems) {
2008 final List<Entry> entries = new ArrayList<>();
2009
2010 for (List<DataItem> bucket : dataItemsToBucket(dataItems).values()) {
2011 for (DataItem dataItem : bucket) {
2012 if (GPLUS_PROFILE_DATA_5_VIEW_PROFILE.equals(
2013 dataItem.getContentValues().getAsString(Data.DATA5))) {
2014 final Entry entry = dataItemToEntry(dataItem, /* secondDataItem = */ null,
2015 this, mContactData, /* aboutCardName = */ null);
2016 if (entry != null) {
2017 entries.add(entry);
2018 }
2019 }
2020 }
2021 }
2022 return entries;
2023 }
2024
2025 /**
2026 * For Hangouts entries, a single ExpandingEntryCardView.Entry consists of two data items. This
2027 * method attempts to build each entry using the two data items if they are available. If there
2028 * are more or less than two data items, a fall back is used and each data item gets its own
2029 * entry.
2030 */
2031 private List<Entry> hangoutsDataItemsToEntries(List<DataItem> dataItems) {
2032 final List<Entry> entries = new ArrayList<>();
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002033
2034 // Use the buckets to build entries. If a bucket contains two data items, build the special
2035 // entry, otherwise fall back to the normal entry.
yaolu3904fb52016-08-05 13:32:56 -07002036 for (List<DataItem> bucket : dataItemsToBucket(dataItems).values()) {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002037 if (bucket.size() == 2) {
2038 // Use the pair to build an entry
2039 final Entry entry = dataItemToEntry(bucket.get(0),
2040 /* secondDataItem = */ bucket.get(1), this, mContactData,
2041 /* aboutCardName = */ null);
2042 if (entry != null) {
2043 entries.add(entry);
2044 }
2045 } else {
2046 for (DataItem dataItem : bucket) {
2047 final Entry entry = dataItemToEntry(dataItem, /* secondDataItem = */ null,
2048 this, mContactData, /* aboutCardName = */ null);
2049 if (entry != null) {
2050 entries.add(entry);
2051 }
2052 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07002053 }
2054 }
2055 return entries;
Daniel Lehmannedb576a2011-07-27 16:45:13 -07002056 }
2057
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002058 /**
yaolu3904fb52016-08-05 13:32:56 -07002059 * Used for statically passing around Hangouts data items and entry fields to
2060 * populateHangoutsDataItemModel.
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002061 */
yaolu3904fb52016-08-05 13:32:56 -07002062 private static final class HangoutsDataItemModel {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002063 public Intent intent;
2064 public Intent alternateIntent;
2065 public DataItem dataItem;
2066 public DataItem secondDataItem;
2067 public StringBuilder alternateContentDescription;
2068 public String header;
2069 public String text;
2070 public Context context;
2071
yaolu3904fb52016-08-05 13:32:56 -07002072 public HangoutsDataItemModel(Intent intent, Intent alternateIntent, DataItem dataItem,
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002073 DataItem secondDataItem, StringBuilder alternateContentDescription, String header,
2074 String text, Context context) {
2075 this.intent = intent;
2076 this.alternateIntent = alternateIntent;
2077 this.dataItem = dataItem;
2078 this.secondDataItem = secondDataItem;
2079 this.alternateContentDescription = alternateContentDescription;
2080 this.header = header;
2081 this.text = text;
2082 this.context = context;
2083 }
2084 }
2085
yaolu3904fb52016-08-05 13:32:56 -07002086 private static void populateHangoutsDataItemModel(
2087 HangoutsDataItemModel dataModel) {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002088 final Intent secondIntent = new Intent(Intent.ACTION_VIEW);
2089 secondIntent.setDataAndType(ContentUris.withAppendedId(Data.CONTENT_URI,
2090 dataModel.secondDataItem.getId()), dataModel.secondDataItem.getMimeType());
yaolu79525d02016-08-24 12:08:39 -07002091 secondIntent.putExtra(EXTRA_ACTION_TYPE, ActionType.THIRD_PARTY);
yaolua92b2d12016-09-21 17:09:23 -07002092 secondIntent.putExtra(EXTRA_THIRD_PARTY_ACTION, dataModel.secondDataItem.getMimeType());
yaolu79525d02016-08-24 12:08:39 -07002093
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002094 // There is no guarantee the order the data items come in. Second
2095 // data item does not necessarily mean it's the alternate.
yaolu3904fb52016-08-05 13:32:56 -07002096 // Hangouts video should be alternate. Swap if needed
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002097 if (HANGOUTS_DATA_5_VIDEO.equals(
yaolu3904fb52016-08-05 13:32:56 -07002098 dataModel.dataItem.getContentValues().getAsString(Data.DATA5))) {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002099 dataModel.alternateIntent = dataModel.intent;
2100 dataModel.alternateContentDescription = new StringBuilder(dataModel.header);
2101
2102 dataModel.intent = secondIntent;
yaolua92b2d12016-09-21 17:09:23 -07002103 dataModel.header = dataModel.secondDataItem.buildDataStringForDisplay(
2104 dataModel.context, dataModel.secondDataItem.getDataKind());
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002105 dataModel.text = dataModel.secondDataItem.getDataKind().typeColumn;
2106 } else if (HANGOUTS_DATA_5_MESSAGE.equals(
yaolu3904fb52016-08-05 13:32:56 -07002107 dataModel.dataItem.getContentValues().getAsString(Data.DATA5))) {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002108 dataModel.alternateIntent = secondIntent;
2109 dataModel.alternateContentDescription = new StringBuilder(
2110 dataModel.secondDataItem.buildDataStringForDisplay(dataModel.context,
2111 dataModel.secondDataItem.getDataKind()));
2112 }
2113 }
2114
Brian Attwell6bb01342014-08-20 23:16:00 -07002115 private static String getIntentResolveLabel(Intent intent, Context context) {
2116 final List<ResolveInfo> matches = context.getPackageManager().queryIntentActivities(intent,
Paul Soulosdd7419d2014-07-15 11:22:13 -07002117 PackageManager.MATCH_DEFAULT_ONLY);
2118
2119 // Pick first match, otherwise best found
2120 ResolveInfo bestResolve = null;
2121 final int size = matches.size();
2122 if (size == 1) {
2123 bestResolve = matches.get(0);
2124 } else if (size > 1) {
Brian Attwell6bb01342014-08-20 23:16:00 -07002125 bestResolve = ResolveCache.getInstance(context).getBestResolve(intent, matches);
Paul Soulosdd7419d2014-07-15 11:22:13 -07002126 }
2127
2128 if (bestResolve == null) {
2129 return null;
2130 }
2131
Brian Attwell6bb01342014-08-20 23:16:00 -07002132 return String.valueOf(bestResolve.loadLabel(context.getPackageManager()));
Paul Soulosdd7419d2014-07-15 11:22:13 -07002133 }
2134
Daniel Lehmannedb576a2011-07-27 16:45:13 -07002135 /**
Brian Attwell31b2d422014-06-05 00:14:58 -07002136 * Asynchronously extract the most vibrant color from the PhotoView. Once extracted,
2137 * apply this tint to {@link MultiShrinkScroller}. This operation takes about 20-30ms
2138 * on a Nexus 5.
2139 */
2140 private void extractAndApplyTintFromPhotoViewAsynchronously() {
2141 if (mScroller == null) {
2142 return;
2143 }
2144 final Drawable imageViewDrawable = mPhotoView.getDrawable();
Brian Attwellfaf97392014-07-10 18:32:30 -07002145 new AsyncTask<Void, Void, MaterialPalette>() {
Brian Attwell31b2d422014-06-05 00:14:58 -07002146 @Override
Brian Attwellfaf97392014-07-10 18:32:30 -07002147 protected MaterialPalette doInBackground(Void... params) {
2148
Jay Shrauner929a62f2014-11-13 12:03:50 -08002149 if (imageViewDrawable instanceof BitmapDrawable && mContactData != null
Brian Attwell95c268e2014-08-26 22:04:15 -07002150 && mContactData.getThumbnailPhotoBinaryData() != null
2151 && mContactData.getThumbnailPhotoBinaryData().length > 0) {
2152 // Perform the color analysis on the thumbnail instead of the full sized
2153 // image, so that our results will be as similar as possible to the Bugle
2154 // app.
2155 final Bitmap bitmap = BitmapFactory.decodeByteArray(
2156 mContactData.getThumbnailPhotoBinaryData(), 0,
2157 mContactData.getThumbnailPhotoBinaryData().length);
2158 try {
2159 final int primaryColor = colorFromBitmap(bitmap);
2160 if (primaryColor != 0) {
2161 return mMaterialColorMapUtils.calculatePrimaryAndSecondaryColor(
2162 primaryColor);
2163 }
2164 } finally {
2165 bitmap.recycle();
Brian Attwellfaf97392014-07-10 18:32:30 -07002166 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002167 }
2168 if (imageViewDrawable instanceof LetterTileDrawable) {
Brian Attwellfaf97392014-07-10 18:32:30 -07002169 final int primaryColor = ((LetterTileDrawable) imageViewDrawable).getColor();
Brian Attwella3859ed2014-07-15 13:51:55 -07002170 return mMaterialColorMapUtils.calculatePrimaryAndSecondaryColor(primaryColor);
Brian Attwell31b2d422014-06-05 00:14:58 -07002171 }
Brian Attwella3859ed2014-07-15 13:51:55 -07002172 return MaterialColorMapUtils.getDefaultPrimaryAndSecondaryColors(getResources());
Brian Attwell31b2d422014-06-05 00:14:58 -07002173 }
2174
2175 @Override
Brian Attwellfaf97392014-07-10 18:32:30 -07002176 protected void onPostExecute(MaterialPalette palette) {
2177 super.onPostExecute(palette);
Brian Attwell8571dd32014-06-23 23:29:10 -07002178 if (mHasComputedThemeColor) {
2179 // If we had previously computed a theme color from the contact photo,
2180 // then do not update the theme color. Changing the theme color several
2181 // seconds after QC has started, as a result of an updated/upgraded photo,
2182 // is a jarring experience. On the other hand, changing the theme color after
2183 // a rotation or onNewIntent() is perfectly fine.
2184 return;
2185 }
2186 // Check that the Photo has not changed. If it has changed, the new tint
2187 // color needs to be extracted
2188 if (imageViewDrawable == mPhotoView.getDrawable()) {
2189 mHasComputedThemeColor = true;
Brian Attwellfaf97392014-07-10 18:32:30 -07002190 setThemeColor(palette);
Brian Attwell31b2d422014-06-05 00:14:58 -07002191 }
2192 }
2193 }.execute();
2194 }
2195
Brian Attwellfaf97392014-07-10 18:32:30 -07002196 private void setThemeColor(MaterialPalette palette) {
Brian Attwell9b889e62014-06-23 18:25:32 -07002197 // If the color is invalid, use the predefined default
Brian Attwell8e29faf2015-01-21 10:22:40 -08002198 mColorFilterColor = palette.mPrimaryColor;
2199 mScroller.setHeaderTintColor(mColorFilterColor);
Brian Attwellfaf97392014-07-10 18:32:30 -07002200 mStatusBarColor = palette.mSecondaryColor;
Brian Attwell9b889e62014-06-23 18:25:32 -07002201 updateStatusBarColor();
Brian Attwell8571dd32014-06-23 23:29:10 -07002202
Brian Attwell9b889e62014-06-23 18:25:32 -07002203 mColorFilter =
Brian Attwell8e29faf2015-01-21 10:22:40 -08002204 new PorterDuffColorFilter(mColorFilterColor, PorterDuff.Mode.SRC_ATOP);
2205 mContactCard.setColorAndFilter(mColorFilterColor, mColorFilter);
2206 mRecentCard.setColorAndFilter(mColorFilterColor, mColorFilter);
2207 mAboutCard.setColorAndFilter(mColorFilterColor, mColorFilter);
Brian Attwell9b889e62014-06-23 18:25:32 -07002208 }
2209
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002210 private void updateStatusBarColor() {
Wenyi Wang4c3d3e22015-12-17 14:30:02 -08002211 if (mScroller == null || !CompatUtils.isLollipopCompatible()) {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002212 return;
2213 }
2214 final int desiredStatusBarColor;
2215 // Only use a custom status bar color if QuickContacts touches the top of the viewport.
2216 if (mScroller.getScrollNeededToBeFullScreen() <= 0) {
2217 desiredStatusBarColor = mStatusBarColor;
2218 } else {
2219 desiredStatusBarColor = Color.TRANSPARENT;
2220 }
2221 // Animate to the new color.
Brian Attwell847bf2c2014-07-24 13:59:27 -07002222 final ObjectAnimator animation = ObjectAnimator.ofInt(getWindow(), "statusBarColor",
2223 getWindow().getStatusBarColor(), desiredStatusBarColor);
2224 animation.setDuration(ANIMATION_STATUS_BAR_COLOR_CHANGE_DURATION);
2225 animation.setEvaluator(new ArgbEvaluator());
2226 animation.start();
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002227 }
2228
2229 private int colorFromBitmap(Bitmap bitmap) {
2230 // Author of Palette recommends using 24 colors when analyzing profile photos.
2231 final int NUMBER_OF_PALETTE_COLORS = 24;
2232 final Palette palette = Palette.generate(bitmap, NUMBER_OF_PALETTE_COLORS);
Brian Attwella0f20f72014-07-07 11:51:52 -07002233 if (palette != null && palette.getVibrantSwatch() != null) {
2234 return palette.getVibrantSwatch().getRgb();
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002235 }
2236 return 0;
2237 }
2238
Paul Soulosb3054e52014-06-05 16:46:02 -07002239 private List<Entry> contactInteractionsToEntries(List<ContactInteraction> interactions) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07002240 final List<Entry> entries = new ArrayList<>();
Paul Soulosb3054e52014-06-05 16:46:02 -07002241 for (ContactInteraction interaction : interactions) {
Brian Attwelled188282015-02-11 14:12:41 -08002242 if (interaction == null) {
2243 continue;
2244 }
Paul Soulosea5e0b72014-07-08 18:09:44 -07002245 entries.add(new Entry(/* id = */ -1,
2246 interaction.getIcon(this),
Paul Soulosb3054e52014-06-05 16:46:02 -07002247 interaction.getViewHeader(this),
2248 interaction.getViewBody(this),
2249 interaction.getBodyIcon(this),
2250 interaction.getViewFooter(this),
2251 interaction.getFooterIcon(this),
Paul Soulos23e28362014-08-29 14:57:08 -07002252 interaction.getContentDescription(this),
Paul Soulosb3054e52014-06-05 16:46:02 -07002253 interaction.getIntent(),
Paul Soulosdd7419d2014-07-15 11:22:13 -07002254 /* alternateIcon = */ null,
2255 /* alternateIntent = */ null,
2256 /* alternateContentDescription = */ null,
Paul Soulos48ebbaa2014-07-15 13:11:23 -07002257 /* shouldApplyColor = */ true,
Paul Soulos2a4207f2014-07-31 17:09:05 -07002258 /* isEditable = */ false,
Paul Soulos48fc9122014-08-26 13:52:36 -07002259 /* EntryContextMenuInfo = */ null,
2260 /* thirdIcon = */ null,
2261 /* thirdIntent = */ null,
Paul Soulos48290be2014-09-08 13:44:51 -07002262 /* thirdContentDescription = */ null,
Tyler Gunn5f87e922015-08-05 14:24:52 -07002263 /* thirdAction = */ Entry.ACTION_NONE,
2264 /* thirdActionExtras = */ null,
Paul Soulos48290be2014-09-08 13:44:51 -07002265 interaction.getIconResourceId()));
Paul Soulosb3054e52014-06-05 16:46:02 -07002266 }
2267 return entries;
2268 }
2269
Paul Souloseb64a4b2014-07-07 17:03:27 -07002270 private final LoaderCallbacks<Contact> mLoaderContactCallbacks =
Maurice Chu851222a2012-06-21 11:43:08 -07002271 new LoaderCallbacks<Contact>() {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002272 @Override
Maurice Chu851222a2012-06-21 11:43:08 -07002273 public void onLoaderReset(Loader<Contact> loader) {
Paul Soulos405ae402014-07-14 16:16:36 -07002274 mContactData = null;
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002275 }
2276
2277 @Override
Maurice Chu851222a2012-06-21 11:43:08 -07002278 public void onLoadFinished(Loader<Contact> loader, Contact data) {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002279 Trace.beginSection("onLoadFinished()");
Brian Attwell930da3a2014-10-16 21:01:21 -07002280 try {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002281
Brian Attwell930da3a2014-10-16 21:01:21 -07002282 if (isFinishing()) {
2283 return;
2284 }
2285 if (data.isError()) {
Jay Shrauner02ecc3f2014-12-12 11:29:16 -08002286 // This means either the contact is invalid or we had an
2287 // internal error such as an acore crash.
2288 Log.i(TAG, "Failed to load contact: " + ((ContactLoader)loader).getLookupUri());
2289 Toast.makeText(QuickContactActivity.this, R.string.invalidContactMessage,
2290 Toast.LENGTH_LONG).show();
2291 finish();
2292 return;
Brian Attwell930da3a2014-10-16 21:01:21 -07002293 }
2294 if (data.isNotFound()) {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002295 Log.i(TAG, "No contact found: " + ((ContactLoader)loader).getLookupUri());
2296 Toast.makeText(QuickContactActivity.this, R.string.invalidContactMessage,
2297 Toast.LENGTH_LONG).show();
Brian Attwell930da3a2014-10-16 21:01:21 -07002298 finish();
2299 return;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002300 }
Brian Attwell930da3a2014-10-16 21:01:21 -07002301
Marcus Hagerott677ee2b2016-10-28 15:31:55 -07002302 if (!mIsRecreatedInstance && !mShortcutUsageReported && data != null) {
Marcus Hagerottd105c1e2016-09-30 14:28:00 -07002303 mShortcutUsageReported = true;
2304 DynamicShortcuts.reportShortcutUsed(QuickContactActivity.this,
2305 data.getLookupKey());
2306 }
Brian Attwell930da3a2014-10-16 21:01:21 -07002307 bindContactData(data);
2308
2309 } finally {
2310 Trace.endSection();
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002311 }
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002312 }
2313
2314 @Override
Maurice Chu851222a2012-06-21 11:43:08 -07002315 public Loader<Contact> onCreateLoader(int id, Bundle args) {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002316 if (mLookupUri == null) {
2317 Log.wtf(TAG, "Lookup uri wasn't initialized. Loader was started too early");
2318 }
Brian Attwelld28851f2014-06-10 13:25:07 -07002319 // Load all contact data. We need loadGroupMetaData=true to determine whether the
2320 // contact is invisible. If it is, we need to display an "Add to Contacts" MenuItem.
Yorke Leeb2b435a2012-11-12 16:47:06 -08002321 return new ContactLoader(getApplicationContext(), mLookupUri,
Marcus Hagerott881ffc02016-12-09 13:34:03 -08002322 true /*loadGroupMetaData*/, true /*postViewNotification*/,
2323 true /*computeFormattedPhoneNumber*/);
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002324 }
2325 };
Paul Soulosb3054e52014-06-05 16:46:02 -07002326
Brian Attwellb7e43642014-06-02 14:33:04 -07002327 @Override
2328 public void onBackPressed() {
Tingting Wangac9596e2016-08-02 22:24:24 -07002329 final int previousScreenType = getIntent().getIntExtra
2330 (EXTRA_PREVIOUS_SCREEN_TYPE, ScreenType.UNKNOWN);
2331 if ((previousScreenType == ScreenType.ALL_CONTACTS
2332 || previousScreenType == ScreenType.FAVORITES)
2333 && !SharedPreferenceUtil.getHamburgerPromoTriggerActionHappenedBefore(this)) {
2334 SharedPreferenceUtil.setHamburgerPromoTriggerActionHappenedBefore(this);
2335 }
Brian Attwellb7e43642014-06-02 14:33:04 -07002336 if (mScroller != null) {
Brian Attwell8477eaf2014-06-18 15:39:50 -07002337 if (!mIsExitAnimationInProgress) {
2338 mScroller.scrollOffBottom();
2339 }
Brian Attwellb7e43642014-06-02 14:33:04 -07002340 } else {
2341 super.onBackPressed();
2342 }
2343 }
Paul Soulosb3054e52014-06-05 16:46:02 -07002344
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002345 @Override
2346 public void finish() {
2347 super.finish();
2348
2349 // override transitions to skip the standard window animations
2350 overridePendingTransition(0, 0);
2351 }
2352
Paul Souloseb64a4b2014-07-07 17:03:27 -07002353 private final LoaderCallbacks<List<ContactInteraction>> mLoaderInteractionsCallbacks =
Paul Soulosb3054e52014-06-05 16:46:02 -07002354 new LoaderCallbacks<List<ContactInteraction>>() {
2355
2356 @Override
2357 public Loader<List<ContactInteraction>> onCreateLoader(int id, Bundle args) {
Paul Soulosb3054e52014-06-05 16:46:02 -07002358 Loader<List<ContactInteraction>> loader = null;
2359 switch (id) {
2360 case LOADER_SMS_ID:
Paul Soulosb3054e52014-06-05 16:46:02 -07002361 loader = new SmsInteractionsLoader(
2362 QuickContactActivity.this,
Paul Soulosab840442014-06-17 14:08:40 -07002363 args.getStringArray(KEY_LOADER_EXTRA_PHONES),
Paul Soulosb3054e52014-06-05 16:46:02 -07002364 MAX_SMS_RETRIEVE);
2365 break;
Paul Soulos899aa212014-06-11 12:04:43 -07002366 case LOADER_CALENDAR_ID:
Paul Soulosae4cafe2014-07-09 14:11:03 -07002367 final String[] emailsArray = args.getStringArray(KEY_LOADER_EXTRA_EMAILS);
2368 List<String> emailsList = null;
2369 if (emailsArray != null) {
2370 emailsList = Arrays.asList(args.getStringArray(KEY_LOADER_EXTRA_EMAILS));
2371 }
Paul Soulos899aa212014-06-11 12:04:43 -07002372 loader = new CalendarInteractionsLoader(
2373 QuickContactActivity.this,
Paul Soulosae4cafe2014-07-09 14:11:03 -07002374 emailsList,
Paul Soulos899aa212014-06-11 12:04:43 -07002375 MAX_FUTURE_CALENDAR_RETRIEVE,
2376 MAX_PAST_CALENDAR_RETRIEVE,
2377 FUTURE_MILLISECOND_TO_SEARCH_LOCAL_CALENDAR,
2378 PAST_MILLISECOND_TO_SEARCH_LOCAL_CALENDAR);
2379 break;
Paul Soulosab840442014-06-17 14:08:40 -07002380 case LOADER_CALL_LOG_ID:
Paul Soulosab840442014-06-17 14:08:40 -07002381 loader = new CallLogInteractionsLoader(
2382 QuickContactActivity.this,
2383 args.getStringArray(KEY_LOADER_EXTRA_PHONES),
guanxiongliuc7a4b9c2016-04-30 20:19:21 -07002384 args.getStringArray(KEY_LOADER_EXTRA_SIP_NUMBERS),
Paul Soulosab840442014-06-17 14:08:40 -07002385 MAX_CALL_LOG_RETRIEVE);
Paul Soulosb3054e52014-06-05 16:46:02 -07002386 }
2387 return loader;
2388 }
2389
2390 @Override
2391 public void onLoadFinished(Loader<List<ContactInteraction>> loader,
2392 List<ContactInteraction> data) {
Paul Soulosb3054e52014-06-05 16:46:02 -07002393 mRecentLoaderResults.put(loader.getId(), data);
2394
2395 if (isAllRecentDataLoaded()) {
2396 bindRecentData();
2397 }
2398 }
2399
2400 @Override
2401 public void onLoaderReset(Loader<List<ContactInteraction>> loader) {
2402 mRecentLoaderResults.remove(loader.getId());
2403 }
Paul Soulosb3054e52014-06-05 16:46:02 -07002404 };
2405
2406 private boolean isAllRecentDataLoaded() {
2407 return mRecentLoaderResults.size() == mRecentLoaderIds.length;
2408 }
2409
2410 private void bindRecentData() {
Paul Souloseb64a4b2014-07-07 17:03:27 -07002411 final List<ContactInteraction> allInteractions = new ArrayList<>();
Brian Attwell30e1ef12014-09-02 15:49:20 -07002412 final List<List<Entry>> interactionsWrapper = new ArrayList<>();
Paul Soulosb3054e52014-06-05 16:46:02 -07002413
Brian Attwelled188282015-02-11 14:12:41 -08002414 // Serialize mRecentLoaderResults into a single list. This should be done on the main
2415 // thread to avoid races against mRecentLoaderResults edits.
2416 for (List<ContactInteraction> loaderInteractions : mRecentLoaderResults.values()) {
2417 allInteractions.addAll(loaderInteractions);
2418 }
2419
Brian Attwell30e1ef12014-09-02 15:49:20 -07002420 mRecentDataTask = new AsyncTask<Void, Void, Void>() {
Paul Soulosb3054e52014-06-05 16:46:02 -07002421 @Override
Brian Attwell30e1ef12014-09-02 15:49:20 -07002422 protected Void doInBackground(Void... params) {
2423 Trace.beginSection("sort recent loader results");
Paul Soulosb3054e52014-06-05 16:46:02 -07002424
Brian Attwell30e1ef12014-09-02 15:49:20 -07002425 // Sort the interactions by most recent
2426 Collections.sort(allInteractions, new Comparator<ContactInteraction>() {
2427 @Override
2428 public int compare(ContactInteraction a, ContactInteraction b) {
Brian Attwelled188282015-02-11 14:12:41 -08002429 if (a == null && b == null) {
2430 return 0;
2431 }
2432 if (a == null) {
2433 return 1;
2434 }
2435 if (b == null) {
2436 return -1;
2437 }
2438 if (a.getInteractionDate() > b.getInteractionDate()) {
2439 return -1;
2440 }
2441 if (a.getInteractionDate() == b.getInteractionDate()) {
2442 return 0;
2443 }
2444 return 1;
Brian Attwell30e1ef12014-09-02 15:49:20 -07002445 }
2446 });
2447
2448 Trace.endSection();
2449 Trace.beginSection("contactInteractionsToEntries");
2450
2451 // Wrap each interaction in its own list so that an icon is displayed for each entry
2452 for (Entry contactInteraction : contactInteractionsToEntries(allInteractions)) {
2453 List<Entry> entryListWrapper = new ArrayList<>(1);
2454 entryListWrapper.add(contactInteraction);
2455 interactionsWrapper.add(entryListWrapper);
2456 }
2457
2458 Trace.endSection();
2459 return null;
2460 }
2461
2462 @Override
2463 protected void onPostExecute(Void aVoid) {
2464 super.onPostExecute(aVoid);
2465 Trace.beginSection("initialize recents card");
2466
2467 if (allInteractions.size() > 0) {
2468 mRecentCard.initialize(interactionsWrapper,
Paul Soulosb3054e52014-06-05 16:46:02 -07002469 /* numInitialVisibleEntries = */ MIN_NUM_COLLAPSED_RECENT_ENTRIES_SHOWN,
Paul Soulosc8e2a912014-07-21 14:52:20 -07002470 /* isExpanded = */ mRecentCard.isExpanded(), /* isAlwaysExpanded = */ false,
Brian Attwell30e1ef12014-09-02 15:49:20 -07002471 mExpandingEntryCardViewListener, mScroller);
yaolu79525d02016-08-24 12:08:39 -07002472 if (mRecentCard.getVisibility() == View.GONE && mShouldLog) {
2473 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.RECENT,
2474 ActionType.UNKNOWN_ACTION, /* thirdPartyAction */ null);
2475 }
Brian Attwell30e1ef12014-09-02 15:49:20 -07002476 mRecentCard.setVisibility(View.VISIBLE);
yaolu38fa2852016-11-10 13:08:58 -08002477 } else {
2478 mRecentCard.setVisibility(View.GONE);
Brian Attwell30e1ef12014-09-02 15:49:20 -07002479 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07002480
Brian Attwell30e1ef12014-09-02 15:49:20 -07002481 Trace.endSection();
guanxiongliu04a19362016-05-18 14:38:51 -07002482 Trace.beginSection("initialize permission explanation card");
2483
John Shaobd9ef3c2016-12-15 12:42:03 -08002484 final Drawable historyIcon = getResources().getDrawable(
2485 R.drawable.quantum_ic_history_vd_theme_24);
guanxiongliu8e56edf2016-07-14 13:48:55 -07002486
guanxiongliu04a19362016-05-18 14:38:51 -07002487 final Entry permissionExplanationEntry = new Entry(CARD_ENTRY_ID_REQUEST_PERMISSION,
2488 historyIcon, getString(R.string.permission_explanation_header),
2489 mPermissionExplanationCardSubHeader, /* subHeaderIcon = */ null,
2490 /* text = */ null, /* textIcon = */ null,
2491 /* primaryContentDescription = */ null, getIntent(),
2492 /* alternateIcon = */ null, /* alternateIntent = */ null,
2493 /* alternateContentDescription = */ null, /* shouldApplyColor = */ true,
2494 /* isEditable = */ false, /* EntryContextMenuInfo = */ null,
2495 /* thirdIcon = */ null, /* thirdIntent = */ null,
2496 /* thirdContentDescription = */ null, /* thirdAction = */ Entry.ACTION_NONE,
John Shaobd9ef3c2016-12-15 12:42:03 -08002497 /* thirdExtras = */ null, R.drawable.quantum_ic_history_vd_theme_24);
guanxiongliu04a19362016-05-18 14:38:51 -07002498
2499 final List<List<Entry>> permissionExplanationEntries = new ArrayList<>();
2500 permissionExplanationEntries.add(new ArrayList<Entry>());
2501 permissionExplanationEntries.get(0).add(permissionExplanationEntry);
2502
2503 final int subHeaderTextColor = getResources().getColor(android.R.color.white);
2504 final PorterDuffColorFilter whiteColorFilter =
2505 new PorterDuffColorFilter(subHeaderTextColor, PorterDuff.Mode.SRC_ATOP);
2506
2507 mPermissionExplanationCard.initialize(permissionExplanationEntries,
2508 /* numInitialVisibleEntries = */ 1,
2509 /* isExpanded = */ true,
2510 /* isAlwaysExpanded = */ true,
2511 /* listener = */ null,
2512 mScroller);
2513
2514 mPermissionExplanationCard.setColorAndFilter(subHeaderTextColor, whiteColorFilter);
2515 mPermissionExplanationCard.setBackgroundColor(mColorFilterColor);
2516 mPermissionExplanationCard.setEntryHeaderColor(subHeaderTextColor);
2517 mPermissionExplanationCard.setEntrySubHeaderColor(subHeaderTextColor);
2518
2519 if (mShouldShowPermissionExplanation) {
yaolu79525d02016-08-24 12:08:39 -07002520 if (mPermissionExplanationCard.getVisibility() == View.GONE
2521 && mShouldLog) {
2522 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.PERMISSION,
2523 ActionType.UNKNOWN_ACTION, /* thirdPartyAction */ null);
2524 }
guanxiongliu04a19362016-05-18 14:38:51 -07002525 mPermissionExplanationCard.setVisibility(View.VISIBLE);
2526 } else {
2527 mPermissionExplanationCard.setVisibility(View.GONE);
2528 }
2529
2530 Trace.endSection();
Brian Attwell30e1ef12014-09-02 15:49:20 -07002531
2532 // About card is initialized along with the contact card, but since it appears after
2533 // the recent card in the UI, we hold off until making it visible until the recent
2534 // card is also ready to avoid stuttering.
2535 if (mAboutCard.shouldShow()) {
2536 mAboutCard.setVisibility(View.VISIBLE);
2537 } else {
2538 mAboutCard.setVisibility(View.GONE);
2539 }
2540 mRecentDataTask = null;
2541 }
2542 };
2543 mRecentDataTask.execute();
Paul Soulosb3054e52014-06-05 16:46:02 -07002544 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002545
2546 @Override
2547 protected void onStop() {
2548 super.onStop();
2549
2550 if (mEntriesAndActionsTask != null) {
2551 // Once the activity is stopped, we will no longer want to bind mEntriesAndActionsTask's
2552 // results on the UI thread. In some circumstances Activities are killed without
2553 // onStop() being called. This is not a problem, because in these circumstances
2554 // the entire process will be killed.
2555 mEntriesAndActionsTask.cancel(/* mayInterruptIfRunning = */ false);
2556 }
Brian Attwell30e1ef12014-09-02 15:49:20 -07002557 if (mRecentDataTask != null) {
2558 mRecentDataTask.cancel(/* mayInterruptIfRunning = */ false);
2559 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002560 }
Paul Soulos23889ba2014-06-10 14:55:32 -07002561
Tingting Wange3a7c4f2015-09-24 18:23:18 -07002562 @Override
2563 public void onDestroy() {
Wenyi Wang1e5b77f2016-11-29 18:07:13 -08002564 LocalBroadcastManager.getInstance(this).unregisterReceiver(mListener);
Tingting Wange3a7c4f2015-09-24 18:23:18 -07002565 super.onDestroy();
Tingting Wange3a7c4f2015-09-24 18:23:18 -07002566 }
2567
Paul Soulos23889ba2014-06-10 14:55:32 -07002568 /**
Brian Attwelld28851f2014-06-10 13:25:07 -07002569 * Returns true if it is possible to edit the current contact.
2570 */
2571 private boolean isContactEditable() {
2572 return mContactData != null && !mContactData.isDirectoryEntry();
2573 }
2574
Brian Attwella41d6d12014-07-31 15:23:13 -07002575 /**
2576 * Returns true if it is possible to share the current contact.
2577 */
2578 private boolean isContactShareable() {
2579 return mContactData != null && !mContactData.isDirectoryEntry();
2580 }
2581
Brian Attwell60953692014-07-11 17:18:46 -07002582 private Intent getEditContactIntent() {
Gary Mai363af602016-09-28 10:01:23 -07002583 return EditorIntents.createEditContactIntent(QuickContactActivity.this,
Walter Jang1e8801b2015-03-10 15:57:05 -07002584 mContactData.getLookupUri(),
2585 mHasComputedThemeColor
2586 ? new MaterialPalette(mColorFilterColor, mStatusBarColor) : null,
Wenyi Wangc41a1e52016-01-29 17:01:35 +00002587 mContactData.getPhotoId());
Brian Attwell60953692014-07-11 17:18:46 -07002588 }
2589
2590 private void editContact() {
Paul Soulosa42ef762014-08-20 10:26:10 -07002591 mHasIntentLaunched = true;
Zheng Fuc00a0b52014-09-02 16:44:44 -07002592 mContactLoader.cacheResult();
Brian Attwell60953692014-07-11 17:18:46 -07002593 startActivityForResult(getEditContactIntent(), REQUEST_CODE_CONTACT_EDITOR_ACTIVITY);
Brian Attwelld28851f2014-06-10 13:25:07 -07002594 }
2595
Walter Jang5d2e2622014-11-03 13:20:48 -08002596 private void deleteContact() {
2597 final Uri contactUri = mContactData.getLookupUri();
2598 ContactDeletionInteraction.start(this, contactUri, /* finishActivityWhenDone =*/ true);
2599 }
2600
yaolu79525d02016-08-24 12:08:39 -07002601 private void toggleStar(MenuItem starredMenuItem, boolean isStarred) {
2602 // To improve responsiveness, swap out the picture (and tag) in the UI already
2603 ContactDisplayUtils.configureStarredMenuItem(starredMenuItem,
2604 mContactData.isDirectoryEntry(), mContactData.isUserProfile(), !isStarred);
Brian Attwelld28851f2014-06-10 13:25:07 -07002605
yaolu79525d02016-08-24 12:08:39 -07002606 // Now perform the real save
2607 final Intent intent = ContactSaveService.createSetStarredIntent(
2608 QuickContactActivity.this, mContactData.getLookupUri(), !isStarred);
2609 startService(intent);
Brian Attwelld28851f2014-06-10 13:25:07 -07002610
yaolu79525d02016-08-24 12:08:39 -07002611 final CharSequence accessibilityText = !isStarred
2612 ? getResources().getText(R.string.description_action_menu_add_star)
2613 : getResources().getText(R.string.description_action_menu_remove_star);
2614 // Accessibility actions need to have an associated view. We can't access the MenuItem's
2615 // underlying view, so put this accessibility action on the root view.
2616 mScroller.announceForAccessibility(accessibilityText);
Brian Attwelld28851f2014-06-10 13:25:07 -07002617 }
2618
Brian Attwell752cccf2014-06-10 16:24:04 -07002619 private void shareContact() {
2620 final String lookupKey = mContactData.getLookupKey();
Brian Attwell652936f2015-05-27 19:40:36 -07002621 final Uri shareUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey);
Brian Attwell752cccf2014-06-10 16:24:04 -07002622 final Intent intent = new Intent(Intent.ACTION_SEND);
Yorke Lee552ee562015-07-28 15:58:56 -07002623 intent.setType(Contacts.CONTENT_VCARD_TYPE);
Brian Attwell8dcb4ed2015-06-15 15:45:03 -07002624 intent.putExtra(Intent.EXTRA_STREAM, shareUri);
Brian Attwell752cccf2014-06-10 16:24:04 -07002625
2626 // Launch chooser to share contact via
Wenyi Wang281689f2016-05-30 10:32:30 -07002627 final CharSequence chooseTitle = getResources().getQuantityString(
2628 R.plurals.title_share_via, /* quantity */ 1);
Brian Attwell752cccf2014-06-10 16:24:04 -07002629 final Intent chooseIntent = Intent.createChooser(intent, chooseTitle);
2630
2631 try {
Paul Soulosa42ef762014-08-20 10:26:10 -07002632 mHasIntentLaunched = true;
Brian Attwell652936f2015-05-27 19:40:36 -07002633 ImplicitIntentsUtil.startActivityOutsideApp(this, chooseIntent);
Paul Souloseb64a4b2014-07-07 17:03:27 -07002634 } catch (final ActivityNotFoundException ex) {
Brian Attwell752cccf2014-06-10 16:24:04 -07002635 Toast.makeText(this, R.string.share_error, Toast.LENGTH_SHORT).show();
2636 }
2637 }
2638
2639 /**
2640 * Creates a launcher shortcut with the current contact.
2641 */
2642 private void createLauncherShortcutWithContact() {
2643 final ShortcutIntentBuilder builder = new ShortcutIntentBuilder(this,
2644 new OnShortcutIntentCreatedListener() {
2645
2646 @Override
2647 public void onShortcutIntentCreated(Uri uri, Intent shortcutIntent) {
2648 // Broadcast the shortcutIntent to the launcher to create a
2649 // shortcut to this contact
2650 shortcutIntent.setAction(ACTION_INSTALL_SHORTCUT);
2651 QuickContactActivity.this.sendBroadcast(shortcutIntent);
2652
2653 // Send a toast to give feedback to the user that a shortcut to this
2654 // contact was added to the launcher.
guanxiongliuce8fc7b2016-03-28 11:32:32 -07002655 final String displayName = shortcutIntent
2656 .getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
Tingting Wang168331d2015-10-30 12:00:57 -07002657 final String toastMessage = TextUtils.isEmpty(displayName)
2658 ? getString(R.string.createContactShortcutSuccessful_NoName)
2659 : getString(R.string.createContactShortcutSuccessful, displayName);
2660 Toast.makeText(QuickContactActivity.this, toastMessage,
Brian Attwell752cccf2014-06-10 16:24:04 -07002661 Toast.LENGTH_SHORT).show();
2662 }
2663
2664 });
Brian Attwell63176c92014-08-18 15:14:18 -07002665 builder.createContactShortcutIntent(mContactData.getLookupUri());
Brian Attwell752cccf2014-06-10 16:24:04 -07002666 }
2667
Brian Attwell66965e12014-09-08 16:15:20 -07002668 private boolean isShortcutCreatable() {
Jay Shrauner1c06ce72015-05-01 11:56:08 -07002669 if (mContactData == null || mContactData.isUserProfile() ||
2670 mContactData.isDirectoryEntry()) {
Brian Attwell8d0557e2014-12-02 11:18:16 -08002671 return false;
2672 }
Brian Attwell66965e12014-09-08 16:15:20 -07002673 final Intent createShortcutIntent = new Intent();
2674 createShortcutIntent.setAction(ACTION_INSTALL_SHORTCUT);
2675 final List<ResolveInfo> receivers = getPackageManager()
2676 .queryBroadcastReceivers(createShortcutIntent, 0);
2677 return receivers != null && receivers.size() > 0;
2678 }
2679
yaolu58a1ac22016-10-24 16:50:24 -07002680 private void setStateForPhoneMenuItems(Contact contact) {
2681 if (contact != null) {
2682 mSendToVoicemailState = contact.isSendToVoicemail();
2683 mCustomRingtone = contact.getCustomRingtone();
2684 mArePhoneOptionsChangable = isContactEditable()
2685 && PhoneCapabilityTester.isPhone(this);
2686 }
2687 }
2688
Brian Attwelld28851f2014-06-10 13:25:07 -07002689 @Override
2690 public boolean onCreateOptionsMenu(Menu menu) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07002691 final MenuInflater inflater = getMenuInflater();
Brian Attwelld28851f2014-06-10 13:25:07 -07002692 inflater.inflate(R.menu.quickcontact, menu);
2693 return true;
2694 }
2695
2696 @Override
2697 public boolean onPrepareOptionsMenu(Menu menu) {
2698 if (mContactData != null) {
2699 final MenuItem starredMenuItem = menu.findItem(R.id.menu_star);
Paul Soulos333091a2014-07-22 13:54:41 -07002700 ContactDisplayUtils.configureStarredMenuItem(starredMenuItem,
Brian Attwelld28851f2014-06-10 13:25:07 -07002701 mContactData.isDirectoryEntry(), mContactData.isUserProfile(),
2702 mContactData.getStarred());
Brian Attwella41d6d12014-07-31 15:23:13 -07002703
Brian Attwelld28851f2014-06-10 13:25:07 -07002704 // Configure edit MenuItem
2705 final MenuItem editMenuItem = menu.findItem(R.id.menu_edit);
2706 editMenuItem.setVisible(true);
2707 if (DirectoryContactUtil.isDirectoryContact(mContactData) || InvisibleContactUtil
2708 .isInvisibleAndAddable(mContactData, this)) {
John Shaobd9ef3c2016-12-15 12:42:03 -08002709 editMenuItem.setIcon(R.drawable.quantum_ic_person_add_vd_theme_24);
Brian Attwell2e4214c2014-07-10 22:03:16 -07002710 editMenuItem.setTitle(R.string.menu_add_contact);
Brian Attwelld28851f2014-06-10 13:25:07 -07002711 } else if (isContactEditable()) {
John Shaobd9ef3c2016-12-15 12:42:03 -08002712 editMenuItem.setIcon(R.drawable.quantum_ic_create_vd_theme_24);
Brian Attwell2e4214c2014-07-10 22:03:16 -07002713 editMenuItem.setTitle(R.string.menu_editContact);
Brian Attwelld28851f2014-06-10 13:25:07 -07002714 } else {
2715 editMenuItem.setVisible(false);
2716 }
Brian Attwella41d6d12014-07-31 15:23:13 -07002717
Gary Maib9065dd2016-11-08 10:49:00 -08002718 // The link menu item is only visible if this has a single raw contact.
yaolu2de7c8e2016-09-26 09:45:44 -07002719 final MenuItem joinMenuItem = menu.findItem(R.id.menu_join);
2720 joinMenuItem.setVisible(!InvisibleContactUtil.isInvisibleAndAddable(mContactData, this)
Gary Maib9065dd2016-11-08 10:49:00 -08002721 && isContactEditable() && !mContactData.isUserProfile()
2722 && !mContactData.isMultipleRawContacts());
2723
2724 // Viewing linked contacts can only happen if there are multiple raw contacts and
2725 // the link menu isn't available.
2726 final MenuItem linkedContactsMenuItem = menu.findItem(R.id.menu_linked_contacts);
2727 linkedContactsMenuItem.setVisible(mContactData.isMultipleRawContacts()
2728 && !joinMenuItem.isVisible());
yaolu2de7c8e2016-09-26 09:45:44 -07002729
Walter Jang5d2e2622014-11-03 13:20:48 -08002730 final MenuItem deleteMenuItem = menu.findItem(R.id.menu_delete);
Walter Jang82ed2b52015-09-09 12:01:04 -07002731 deleteMenuItem.setVisible(isContactEditable() && !mContactData.isUserProfile());
Walter Jang5d2e2622014-11-03 13:20:48 -08002732
Brian Attwella41d6d12014-07-31 15:23:13 -07002733 final MenuItem shareMenuItem = menu.findItem(R.id.menu_share);
2734 shareMenuItem.setVisible(isContactShareable());
2735
Brian Attwell66965e12014-09-08 16:15:20 -07002736 final MenuItem shortcutMenuItem = menu.findItem(R.id.menu_create_contact_shortcut);
2737 shortcutMenuItem.setVisible(isShortcutCreatable());
2738
yaolu58a1ac22016-10-24 16:50:24 -07002739 // Hide telephony-related settings (ringtone, send to voicemail)
2740 // if we don't have a telephone
2741 final MenuItem ringToneMenuItem = menu.findItem(R.id.menu_set_ringtone);
2742 ringToneMenuItem.setVisible(!mContactData.isUserProfile() && mArePhoneOptionsChangable);
2743
2744 final MenuItem sendToVoiceMailMenuItem = menu.findItem(R.id.menu_send_to_voicemail);
2745 sendToVoiceMailMenuItem.setVisible(!mContactData.isUserProfile()
2746 && mArePhoneOptionsChangable);
2747 sendToVoiceMailMenuItem.setTitle(mSendToVoicemailState
2748 ? R.string.menu_unredirect_calls_to_vm : R.string.menu_redirect_calls_to_vm);
2749
Brian Attwell56bcc2f2015-02-12 23:45:27 +00002750 final MenuItem helpMenu = menu.findItem(R.id.menu_help);
2751 helpMenu.setVisible(HelpUtils.isHelpAndFeedbackAvailable());
2752
Paul Soulos8a6d0022014-06-16 15:11:03 -07002753 return true;
Brian Attwelld28851f2014-06-10 13:25:07 -07002754 }
Paul Soulos8a6d0022014-06-16 15:11:03 -07002755 return false;
Brian Attwelld28851f2014-06-10 13:25:07 -07002756 }
2757
2758 @Override
2759 public boolean onOptionsItemSelected(MenuItem item) {
2760 switch (item.getItemId()) {
2761 case R.id.menu_star:
yaolu79525d02016-08-24 12:08:39 -07002762 // Make sure there is a contact
2763 if (mContactData != null) {
2764 // Read the current starred value from the UI instead of using the last
2765 // loaded state. This allows rapid tapping without writing the same
2766 // value several times
2767 final boolean isStarred = item.isChecked();
2768 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
2769 isStarred ? ActionType.UNSTAR : ActionType.STAR,
2770 /* thirdPartyAction */ null);
2771 toggleStar(item, isStarred);
2772 }
Brian Attwelld28851f2014-06-10 13:25:07 -07002773 return true;
2774 case R.id.menu_edit:
2775 if (DirectoryContactUtil.isDirectoryContact(mContactData)) {
yaolu79525d02016-08-24 12:08:39 -07002776 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
2777 ActionType.ADD, /* thirdPartyAction */ null);
2778
Paul Soulosab54ea12014-08-28 17:22:38 -07002779 // This action is used to launch the contact selector, with the option of
2780 // creating a new contact. Creating a new contact is an INSERT, while selecting
2781 // an exisiting one is an edit. The fields in the edit screen will be
2782 // prepopulated with data.
2783
2784 final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
2785 intent.setType(Contacts.CONTENT_ITEM_TYPE);
2786
Paul Soulosab54ea12014-08-28 17:22:38 -07002787 ArrayList<ContentValues> values = mContactData.getContentValues();
Brian Attwellfc423b42014-10-17 12:58:15 -07002788
2789 // Only pre-fill the name field if the provided display name is an nickname
2790 // or better (e.g. structured name, nickname)
2791 if (mContactData.getDisplayNameSource() >= DisplayNameSources.NICKNAME) {
2792 intent.putExtra(Intents.Insert.NAME, mContactData.getDisplayName());
2793 } else if (mContactData.getDisplayNameSource()
2794 == DisplayNameSources.ORGANIZATION) {
2795 // This is probably an organization. Instead of copying the organization
2796 // name into a name entry, copy it into the organization entry. This
2797 // way we will still consider the contact an organization.
2798 final ContentValues organization = new ContentValues();
2799 organization.put(Organization.COMPANY, mContactData.getDisplayName());
2800 organization.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
2801 values.add(organization);
2802 }
2803
Paul Soulosab54ea12014-08-28 17:22:38 -07002804 // Last time used and times used are aggregated values from the usage stat
2805 // table. They need to be removed from data values so the SQL table can insert
2806 // properly
2807 for (ContentValues value : values) {
2808 value.remove(Data.LAST_TIME_USED);
2809 value.remove(Data.TIMES_USED);
2810 }
2811 intent.putExtra(Intents.Insert.DATA, values);
2812
2813 // If the contact can only export to the same account, add it to the intent.
Gary Mai363af602016-09-28 10:01:23 -07002814 // Otherwise the ContactEditorFragment will show a dialog for selecting
Walter Jang7b0970f2016-09-01 10:40:19 -07002815 // an account.
Paul Soulosab54ea12014-08-28 17:22:38 -07002816 if (mContactData.getDirectoryExportSupport() ==
2817 Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY) {
Brian Attwell4a1c5742015-01-26 15:59:58 -08002818 intent.putExtra(Intents.Insert.EXTRA_ACCOUNT,
Paul Soulosab54ea12014-08-28 17:22:38 -07002819 new Account(mContactData.getDirectoryAccountName(),
2820 mContactData.getDirectoryAccountType()));
Brian Attwell4a1c5742015-01-26 15:59:58 -08002821 intent.putExtra(Intents.Insert.EXTRA_DATA_SET,
Paul Soulosab54ea12014-08-28 17:22:38 -07002822 mContactData.getRawContacts().get(0).getDataSet());
2823 }
2824
Paul Soulosf19dda92014-09-15 15:48:02 -07002825 // Add this flag to disable the delete menu option on directory contact joins
2826 // with local contacts. The delete option is ambiguous when joining contacts.
Walter Jang8bac28b2016-08-30 10:34:55 -07002827 intent.putExtra(
Gary Mai363af602016-09-28 10:01:23 -07002828 ContactEditorFragment.INTENT_EXTRA_DISABLE_DELETE_MENU_OPTION,
Paul Soulosf19dda92014-09-15 15:48:02 -07002829 true);
2830
John Shao41c68862016-08-17 21:02:41 -07002831 intent.setPackage(getPackageName());
Paul Soulosab54ea12014-08-28 17:22:38 -07002832 startActivityForResult(intent, REQUEST_CODE_CONTACT_SELECTION_ACTIVITY);
Brian Attwelld28851f2014-06-10 13:25:07 -07002833 } else if (InvisibleContactUtil.isInvisibleAndAddable(mContactData, this)) {
yaolu79525d02016-08-24 12:08:39 -07002834 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
2835 ActionType.ADD, /* thirdPartyAction */ null);
Brian Attwelld28851f2014-06-10 13:25:07 -07002836 InvisibleContactUtil.addToDefaultGroup(mContactData, this);
2837 } else if (isContactEditable()) {
yaolu79525d02016-08-24 12:08:39 -07002838 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
2839 ActionType.EDIT, /* thirdPartyAction */ null);
Brian Attwelld28851f2014-06-10 13:25:07 -07002840 editContact();
2841 }
2842 return true;
yaolu2de7c8e2016-09-26 09:45:44 -07002843 case R.id.menu_join:
2844 return doJoinContactAction();
Gary Maib9065dd2016-11-08 10:49:00 -08002845 case R.id.menu_linked_contacts:
2846 return showRawContactPickerDialog();
Walter Jang5d2e2622014-11-03 13:20:48 -08002847 case R.id.menu_delete:
yaolu79525d02016-08-24 12:08:39 -07002848 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
yaolu7a09c702016-09-01 18:12:03 -07002849 ActionType.REMOVE, /* thirdPartyAction */ null);
Jay Shrauner1c06ce72015-05-01 11:56:08 -07002850 if (isContactEditable()) {
2851 deleteContact();
2852 }
Walter Jang5d2e2622014-11-03 13:20:48 -08002853 return true;
Brian Attwell752cccf2014-06-10 16:24:04 -07002854 case R.id.menu_share:
yaolu79525d02016-08-24 12:08:39 -07002855 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
2856 ActionType.SHARE, /* thirdPartyAction */ null);
Jay Shrauner49de62f2014-11-26 10:44:28 -08002857 if (isContactShareable()) {
2858 shareContact();
2859 }
Brian Attwell752cccf2014-06-10 16:24:04 -07002860 return true;
2861 case R.id.menu_create_contact_shortcut:
yaolu79525d02016-08-24 12:08:39 -07002862 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
2863 ActionType.SHORTCUT, /* thirdPartyAction */ null);
Jay Shrauner1c06ce72015-05-01 11:56:08 -07002864 if (isShortcutCreatable()) {
2865 createLauncherShortcutWithContact();
2866 }
Brian Attwell752cccf2014-06-10 16:24:04 -07002867 return true;
yaolu58a1ac22016-10-24 16:50:24 -07002868 case R.id.menu_set_ringtone:
2869 doPickRingtone();
2870 return true;
2871 case R.id.menu_send_to_voicemail:
2872 // Update state and save
2873 mSendToVoicemailState = !mSendToVoicemailState;
2874 item.setTitle(mSendToVoicemailState
2875 ? R.string.menu_unredirect_calls_to_vm
2876 : R.string.menu_redirect_calls_to_vm);
2877 final Intent intent = ContactSaveService.createSetSendToVoicemail(
2878 this, mLookupUri, mSendToVoicemailState);
2879 this.startService(intent);
2880 return true;
Brian Attwell56bcc2f2015-02-12 23:45:27 +00002881 case R.id.menu_help:
yaolu79525d02016-08-24 12:08:39 -07002882 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
2883 ActionType.HELP, /* thirdPartyAction */ null);
Brian Attwell56bcc2f2015-02-12 23:45:27 +00002884 HelpUtils.launchHelpAndFeedbackForContactScreen(this);
2885 return true;
Brian Attwelld28851f2014-06-10 13:25:07 -07002886 default:
yaolu79525d02016-08-24 12:08:39 -07002887 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
2888 ActionType.UNKNOWN_ACTION, /* thirdPartyAction */ null);
Brian Attwelld28851f2014-06-10 13:25:07 -07002889 return super.onOptionsItemSelected(item);
2890 }
Paul Soulos23889ba2014-06-10 14:55:32 -07002891 }
yaolu2de7c8e2016-09-26 09:45:44 -07002892
Gary Maib9065dd2016-11-08 10:49:00 -08002893 private boolean showRawContactPickerDialog() {
2894 if (mContactData == null) return false;
2895 startActivityForResult(EditorIntents.createViewLinkedContactsIntent(
2896 QuickContactActivity.this,
2897 mContactData.getLookupUri(),
2898 mHasComputedThemeColor
2899 ? new MaterialPalette(mColorFilterColor, mStatusBarColor)
2900 : null),
2901 REQUEST_CODE_CONTACT_EDITOR_ACTIVITY);
2902 return true;
2903 }
2904
yaolu2de7c8e2016-09-26 09:45:44 -07002905 private boolean doJoinContactAction() {
2906 if (mContactData == null) return false;
2907
2908 mPreviousContactId = mContactData.getId();
2909 final Intent intent = new Intent(this, ContactSelectionActivity.class);
2910 intent.setAction(UiIntentActions.PICK_JOIN_CONTACT_ACTION);
2911 intent.putExtra(UiIntentActions.TARGET_CONTACT_ID_EXTRA_KEY, mPreviousContactId);
2912 startActivityForResult(intent, REQUEST_CODE_JOIN);
2913 return true;
2914 }
2915
2916 /**
2917 * Performs aggregation with the contact selected by the user from suggestions or A-Z list.
2918 */
2919 private void joinAggregate(final long contactId) {
2920 final Intent intent = ContactSaveService.createJoinContactsIntent(
2921 this, mPreviousContactId, contactId, QuickContactActivity.class,
2922 Intent.ACTION_VIEW);
2923 this.startService(intent);
Gary Maib9065dd2016-11-08 10:49:00 -08002924 showLinkProgressBar();
yaolu2de7c8e2016-09-26 09:45:44 -07002925 }
2926
yaolu58a1ac22016-10-24 16:50:24 -07002927
2928 private void doPickRingtone() {
2929 final Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
2930 // Allow user to pick 'Default'
2931 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
2932 // Show only ringtones
2933 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE);
2934 // Allow the user to pick a silent ringtone
2935 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
2936
2937 final Uri ringtoneUri = EditorUiUtils.getRingtoneUriFromString(mCustomRingtone,
2938 CURRENT_API_VERSION);
2939
2940 // Put checkmark next to the current ringtone for this contact
2941 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, ringtoneUri);
2942
2943 // Launch!
2944 try {
2945 startActivityForResult(intent, REQUEST_CODE_PICK_RINGTONE);
2946 } catch (ActivityNotFoundException ex) {
2947 Toast.makeText(this, R.string.missing_app, Toast.LENGTH_SHORT).show();
2948 }
2949 }
Gary Maib9065dd2016-11-08 10:49:00 -08002950
2951 private void dismissProgressBar() {
2952 if (mProgressDialog != null && mProgressDialog.isShowing()) {
2953 mProgressDialog.dismiss();
2954 }
2955 }
2956
2957 private void showLinkProgressBar() {
2958 mProgressDialog.setMessage(getString(R.string.contacts_linking_progress_bar));
2959 mProgressDialog.show();
2960 }
2961
2962 private void showUnlinkProgressBar() {
2963 mProgressDialog.setMessage(getString(R.string.contacts_unlinking_progress_bar));
2964 mProgressDialog.show();
2965 }
2966
2967 private void maybeShowProgressDialog() {
2968 if (ContactSaveService.getState().isActionPending(
2969 ContactSaveService.ACTION_SPLIT_CONTACT)) {
2970 showUnlinkProgressBar();
2971 } else if (ContactSaveService.getState().isActionPending(
2972 ContactSaveService.ACTION_JOIN_CONTACTS)) {
2973 showLinkProgressBar();
2974 }
2975 }
2976
2977 private class SaveServiceListener extends BroadcastReceiver {
2978 @Override
2979 public void onReceive(Context context, Intent intent) {
2980 if (Log.isLoggable(TAG, Log.DEBUG)) {
2981 Log.d(TAG, "Got broadcast from save service " + intent);
2982 }
2983 if (ContactSaveService.BROADCAST_LINK_COMPLETE.equals(intent.getAction())
2984 || ContactSaveService.BROADCAST_UNLINK_COMPLETE.equals(intent.getAction())) {
2985 dismissProgressBar();
2986 if (ContactSaveService.BROADCAST_UNLINK_COMPLETE.equals(intent.getAction())) {
2987 finish();
2988 }
2989 }
2990 }
2991 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -07002992}