blob: 3654c8a0b1fb3471b666330a2d171c570b6c315c [file] [log] [blame]
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.contacts.quickcontact;
18
Paul Soulosab54ea12014-08-28 17:22:38 -070019import android.accounts.Account;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -070020import android.animation.ArgbEvaluator;
Brian Attwellb7e43642014-06-02 14:33:04 -070021import android.animation.ObjectAnimator;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070022import android.app.Activity;
Brian Attwelld28851f2014-06-10 13:25:07 -070023import android.app.Fragment;
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +020024import android.app.LoaderManager.LoaderCallbacks;
Paul Souloseb64a4b2014-07-07 17:03:27 -070025import android.app.SearchManager;
Brian Attwell752cccf2014-06-10 16:24:04 -070026import android.content.ActivityNotFoundException;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070027import android.content.ContentUris;
Paul Soulosea5e0b72014-07-08 18:09:44 -070028import android.content.ContentValues;
Brian Attwell6bb01342014-08-20 23:16:00 -070029import android.content.Context;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070030import android.content.Intent;
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +020031import android.content.Loader;
Paul Soulosdd7419d2014-07-15 11:22:13 -070032import android.content.pm.PackageManager;
33import android.content.pm.ResolveInfo;
Wenyi Wang8d02b162016-01-10 12:43:36 -080034import android.content.res.ColorStateList;
Paul Soulos23e28362014-08-29 14:57:08 -070035import android.content.res.Resources;
Brian Attwell31b2d422014-06-05 00:14:58 -070036import android.graphics.Bitmap;
Brian Attwell95c268e2014-08-26 22:04:15 -070037import android.graphics.BitmapFactory;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -070038import android.graphics.Color;
Paul Souloseb64a4b2014-07-07 17:03:27 -070039import android.graphics.PorterDuff;
40import android.graphics.PorterDuffColorFilter;
Brian Attwell31b2d422014-06-05 00:14:58 -070041import android.graphics.drawable.BitmapDrawable;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -070042import android.graphics.drawable.ColorDrawable;
Brian Attwell31b2d422014-06-05 00:14:58 -070043import android.graphics.drawable.Drawable;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070044import android.net.Uri;
Brian Attwell31b2d422014-06-05 00:14:58 -070045import android.os.AsyncTask;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070046import android.os.Bundle;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -070047import android.os.Trace;
Paul Souloseb64a4b2014-07-07 17:03:27 -070048import android.provider.CalendarContract;
Tingting Wange3a7c4f2015-09-24 18:23:18 -070049import android.provider.ContactsContract;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070050import android.provider.ContactsContract.CommonDataKinds.Email;
Paul Souloseb64a4b2014-07-07 17:03:27 -070051import android.provider.ContactsContract.CommonDataKinds.Event;
52import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
53import android.provider.ContactsContract.CommonDataKinds.Identity;
54import android.provider.ContactsContract.CommonDataKinds.Im;
55import android.provider.ContactsContract.CommonDataKinds.Nickname;
56import android.provider.ContactsContract.CommonDataKinds.Note;
57import android.provider.ContactsContract.CommonDataKinds.Organization;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070058import android.provider.ContactsContract.CommonDataKinds.Phone;
Paul Souloseb64a4b2014-07-07 17:03:27 -070059import android.provider.ContactsContract.CommonDataKinds.Relation;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070060import android.provider.ContactsContract.CommonDataKinds.SipAddress;
61import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
62import android.provider.ContactsContract.CommonDataKinds.Website;
Brian Attwell752cccf2014-06-10 16:24:04 -070063import android.provider.ContactsContract.Contacts;
Paul Soulos60e51082014-07-10 12:33:04 -070064import android.provider.ContactsContract.Data;
Paul Soulosab54ea12014-08-28 17:22:38 -070065import android.provider.ContactsContract.Directory;
Paul Souloseb64a4b2014-07-07 17:03:27 -070066import android.provider.ContactsContract.DisplayNameSources;
Paul Soulosea5e0b72014-07-08 18:09:44 -070067import android.provider.ContactsContract.DataUsageFeedback;
Paul Soulosab54ea12014-08-28 17:22:38 -070068import android.provider.ContactsContract.Intents;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070069import android.provider.ContactsContract.QuickContact;
70import android.provider.ContactsContract.RawContacts;
Wenyi Wang23d9b6e2015-11-19 15:09:06 -080071import android.support.v4.content.ContextCompat;
Brian Attwell31b2d422014-06-05 00:14:58 -070072import android.support.v7.graphics.Palette;
Tingting Wange3a7c4f2015-09-24 18:23:18 -070073import android.support.v7.widget.CardView;
Tyler Gunn03192222014-09-10 15:20:09 -070074import android.telecom.PhoneAccount;
75import android.telecom.TelecomManager;
Brian Attwellc62cc792014-10-02 12:35:07 -070076import android.text.BidiFormatter;
Tingting Wangb6949dc2015-11-03 13:17:12 -080077import android.text.Spannable;
Walter Jang7ce53522014-10-29 13:26:43 -070078import android.text.SpannableString;
Brian Attwellc62cc792014-10-02 12:35:07 -070079import android.text.TextDirectionHeuristics;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070080import android.text.TextUtils;
81import android.util.Log;
Paul Soulos2a4207f2014-07-31 17:09:05 -070082import android.view.ContextMenu;
83import android.view.ContextMenu.ContextMenuInfo;
Tingting Wange3a7c4f2015-09-24 18:23:18 -070084import android.view.LayoutInflater;
Brian Attwelld28851f2014-06-10 13:25:07 -070085import android.view.Menu;
Brian Attwelld28851f2014-06-10 13:25:07 -070086import android.view.MenuInflater;
Paul Souloseb64a4b2014-07-07 17:03:27 -070087import android.view.MenuItem;
Nancy Chenf619e432014-08-18 20:15:12 -070088import android.view.MotionEvent;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070089import android.view.View;
90import android.view.View.OnClickListener;
Paul Soulos2a4207f2014-07-31 17:09:05 -070091import android.view.View.OnCreateContextMenuListener;
Daisuke Miyakawa10d7df72011-07-29 16:11:05 -070092import android.view.WindowManager;
guanxiongliubcae18d2016-02-26 11:04:19 -080093import android.view.accessibility.AccessibilityEvent;
Tingting Wange3a7c4f2015-09-24 18:23:18 -070094import android.widget.Button;
95import android.widget.CheckBox;
96import android.widget.ImageView;
97import android.widget.LinearLayout;
98import android.widget.TextView;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070099import android.widget.Toast;
Brian Attwelld28851f2014-06-10 13:25:07 -0700100import android.widget.Toolbar;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700101
Christine Chen72b3ab12013-08-13 23:22:34 +0200102import com.android.contacts.ContactSaveService;
Brian Attwelld28851f2014-06-10 13:25:07 -0700103import com.android.contacts.ContactsActivity;
Brian Attwell2d150da2014-07-09 22:35:56 -0700104import com.android.contacts.NfcHandler;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -0700105import com.android.contacts.R;
Wenyi Wangd1cb9042015-11-05 17:17:59 -0800106import com.android.contacts.activities.ContactEditorBaseActivity;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700107import com.android.contacts.common.CallUtil;
Paul Soulos2a4207f2014-07-31 17:09:05 -0700108import com.android.contacts.common.ClipboardUtils;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700109import com.android.contacts.common.Collapser;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700110import com.android.contacts.common.ContactPhotoManager;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700111import com.android.contacts.common.ContactsUtils;
Walter Jangf6236372016-02-22 21:24:22 +0000112import com.android.contacts.common.activity.RequestDesiredPermissionsActivity;
Brian Attwellbdd32642015-05-08 17:03:15 -0700113import com.android.contacts.common.activity.RequestPermissionsActivity;
Wenyi Wang4c3d3e22015-12-17 14:30:02 -0800114import com.android.contacts.common.compat.CompatUtils;
Wenyi Wangc85af282015-12-21 15:55:32 -0800115import com.android.contacts.common.compat.EventCompat;
Tyler Gunn5f87e922015-08-05 14:24:52 -0700116import com.android.contacts.common.dialog.CallSubjectDialog;
Brian Attwelld28851f2014-06-10 13:25:07 -0700117import com.android.contacts.common.editor.SelectAccountDialogFragment;
Nancy Chen70e3f4f2014-08-13 16:04:43 -0700118import com.android.contacts.common.interactions.TouchPointManager;
Brian Attwell31b2d422014-06-05 00:14:58 -0700119import com.android.contacts.common.lettertiles.LetterTileDrawable;
Brian Attwell752cccf2014-06-10 16:24:04 -0700120import com.android.contacts.common.list.ShortcutIntentBuilder;
121import com.android.contacts.common.list.ShortcutIntentBuilder.OnShortcutIntentCreatedListener;
Walter Jang62a20742016-02-24 17:04:12 -0800122import com.android.contacts.common.logging.Logger;
123import com.android.contacts.common.logging.ScreenEvent.ScreenType;
Chiao Cheng0d5588d2012-11-26 15:34:14 -0800124import com.android.contacts.common.model.AccountTypeManager;
Yorke Leecd321f62013-10-28 15:20:15 -0700125import com.android.contacts.common.model.Contact;
126import com.android.contacts.common.model.ContactLoader;
127import com.android.contacts.common.model.RawContact;
Chiao Cheng428f0082012-11-13 18:38:56 -0800128import com.android.contacts.common.model.account.AccountType;
Brian Attwelld28851f2014-06-10 13:25:07 -0700129import com.android.contacts.common.model.account.AccountWithDataSet;
Yorke Leecd321f62013-10-28 15:20:15 -0700130import com.android.contacts.common.model.dataitem.DataItem;
Chiao Cheng428f0082012-11-13 18:38:56 -0800131import com.android.contacts.common.model.dataitem.DataKind;
Yorke Leecd321f62013-10-28 15:20:15 -0700132import com.android.contacts.common.model.dataitem.EmailDataItem;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700133import com.android.contacts.common.model.dataitem.EventDataItem;
Yorke Leecd321f62013-10-28 15:20:15 -0700134import com.android.contacts.common.model.dataitem.ImDataItem;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700135import com.android.contacts.common.model.dataitem.NicknameDataItem;
136import com.android.contacts.common.model.dataitem.NoteDataItem;
137import com.android.contacts.common.model.dataitem.OrganizationDataItem;
Paul Soulosb3054e52014-06-05 16:46:02 -0700138import com.android.contacts.common.model.dataitem.PhoneDataItem;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700139import com.android.contacts.common.model.dataitem.RelationDataItem;
140import com.android.contacts.common.model.dataitem.SipAddressDataItem;
141import com.android.contacts.common.model.dataitem.StructuredNameDataItem;
142import com.android.contacts.common.model.dataitem.StructuredPostalDataItem;
143import com.android.contacts.common.model.dataitem.WebsiteDataItem;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700144import com.android.contacts.common.model.ValuesDelta;
Brian Attwellc6100ff2015-02-19 21:35:36 -0800145import com.android.contacts.common.util.ImplicitIntentsUtil;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700146import com.android.contacts.common.util.DateUtils;
Brian Attwellfaf97392014-07-10 18:32:30 -0700147import com.android.contacts.common.util.MaterialColorMapUtils;
148import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
Tyler Gunn5f87e922015-08-05 14:24:52 -0700149import com.android.contacts.common.util.UriUtils;
Brian Attwelld41ab8a2014-08-07 11:08:55 -0700150import com.android.contacts.common.util.ViewUtil;
Paul Soulos333091a2014-07-22 13:54:41 -0700151import com.android.contacts.detail.ContactDisplayUtils;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700152import com.android.contacts.editor.AggregationSuggestionEngine;
153import com.android.contacts.editor.AggregationSuggestionEngine.Suggestion;
Paul Soulosf19dda92014-09-15 15:48:02 -0700154import com.android.contacts.editor.ContactEditorFragment;
Walter Jang5a7a23b2015-03-06 10:54:26 -0800155import com.android.contacts.editor.EditorIntents;
Paul Soulos899aa212014-06-11 12:04:43 -0700156import com.android.contacts.interactions.CalendarInteractionsLoader;
Paul Soulosab840442014-06-17 14:08:40 -0700157import com.android.contacts.interactions.CallLogInteractionsLoader;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700158import com.android.contacts.interactions.ContactDeletionInteraction;
Paul Soulosb3054e52014-06-05 16:46:02 -0700159import com.android.contacts.interactions.ContactInteraction;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700160import com.android.contacts.interactions.JoinContactsDialogFragment;
Wenyi Wang53684892015-10-23 11:11:16 -0700161import com.android.contacts.interactions.JoinContactsDialogFragment.JoinContactsListener;
Paul Soulosb3054e52014-06-05 16:46:02 -0700162import com.android.contacts.interactions.SmsInteractionsLoader;
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700163import com.android.contacts.quickcontact.ExpandingEntryCardView.Entry;
Paul Soulos2a4207f2014-07-31 17:09:05 -0700164import com.android.contacts.quickcontact.ExpandingEntryCardView.EntryContextMenuInfo;
Paul Soulos2ed2a732014-08-12 11:58:39 -0700165import com.android.contacts.quickcontact.ExpandingEntryCardView.EntryTag;
Brian Attwelle8ce6ee2014-06-27 18:26:32 -0700166import com.android.contacts.quickcontact.ExpandingEntryCardView.ExpandingEntryCardViewListener;
Brian Attwell493f8d12014-12-18 12:42:03 -0800167import com.android.contacts.quickcontact.WebAddress.ParseException;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -0700168import com.android.contacts.util.ImageViewDrawableSetter;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700169import com.android.contacts.util.PhoneCapabilityTester;
Brian Attwellb7e43642014-06-02 14:33:04 -0700170import com.android.contacts.util.SchedulingUtils;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700171import com.android.contacts.util.StructuredPostalUtils;
Brian Attwellb7e43642014-06-02 14:33:04 -0700172import com.android.contacts.widget.MultiShrinkScroller;
173import com.android.contacts.widget.MultiShrinkScroller.MultiShrinkScrollerListener;
Brian Attwell02eaf112014-08-27 21:03:42 -0700174import com.android.contacts.widget.QuickContactImageView;
Brian Attwell56bcc2f2015-02-12 23:45:27 +0000175import com.android.contactsbind.HelpUtils;
176
Chiao Chenge0b2f1e2012-06-12 13:07:56 -0700177import com.google.common.collect.Lists;
178
Jay Shrauner82a5a352014-11-24 11:37:44 -0800179import java.lang.SecurityException;
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700180import java.util.ArrayList;
Paul Soulos899aa212014-06-11 12:04:43 -0700181import java.util.Arrays;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700182import java.util.Calendar;
Paul Soulosb3054e52014-06-05 16:46:02 -0700183import java.util.Collections;
184import java.util.Comparator;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700185import java.util.Date;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700186import java.util.HashMap;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700187import java.util.HashSet;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700188import java.util.List;
Paul Soulosb3054e52014-06-05 16:46:02 -0700189import java.util.Map;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700190import java.util.Set;
191import java.util.TreeSet;
Jay Shrauner12ac1e62014-12-08 15:57:19 -0800192import java.util.concurrent.ConcurrentHashMap;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700193
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700194/**
195 * Mostly translucent {@link Activity} that shows QuickContact dialog. It loads
196 * data asynchronously, and then shows a popup with details centered around
197 * {@link Intent#getSourceBounds()}.
198 */
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700199public class QuickContactActivity extends ContactsActivity
Wenyi Wang53684892015-10-23 11:11:16 -0700200 implements AggregationSuggestionEngine.Listener, JoinContactsListener {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700201
202 /**
203 * QuickContacts immediately takes up the full screen. All possible information is shown.
204 * This value for {@link android.provider.ContactsContract.QuickContact#EXTRA_MODE}
205 * should only be used by the Contacts app.
206 */
207 public static final int MODE_FULLY_EXPANDED = 4;
208
Walter Jang62a20742016-02-24 17:04:12 -0800209 /** Used to pass the screen where the user came before launching this Activity. */
210 public static final String EXTRA_PREVIOUS_SCREEN_TYPE = "previous_screen_type";
211
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700212 private static final String TAG = "QuickContact";
213
Brian Attwell9b889e62014-06-23 18:25:32 -0700214 private static final String KEY_THEME_COLOR = "theme_color";
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700215 private static final String KEY_IS_SUGGESTION_LIST_COLLAPSED = "is_suggestion_list_collapsed";
216 private static final String KEY_SELECTED_SUGGESTION_CONTACTS = "selected_suggestion_contacts";
Tingting Wang18906c02015-10-16 14:53:10 -0700217 private static final String KEY_PREVIOUS_CONTACT_ID = "previous_contact_id";
218 private static final String KEY_SUGGESTIONS_AUTO_SELECTED = "suggestions_auto_seleted";
Brian Attwell9b889e62014-06-23 18:25:32 -0700219
Brian Attwell9b889e62014-06-23 18:25:32 -0700220 private static final int ANIMATION_STATUS_BAR_COLOR_CHANGE_DURATION = 150;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700221 private static final int REQUEST_CODE_CONTACT_EDITOR_ACTIVITY = 1;
Brian Attwell0b267fe2014-10-24 19:24:40 +0000222 private static final int SCRIM_COLOR = Color.argb(0xC8, 0, 0, 0);
Paul Soulosab54ea12014-08-28 17:22:38 -0700223 private static final int REQUEST_CODE_CONTACT_SELECTION_ACTIVITY = 2;
Paul Soulosea5e0b72014-07-08 18:09:44 -0700224 private static final String MIMETYPE_SMS = "vnd.android-dir/mms-sms";
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700225
Brian Attwell752cccf2014-06-10 16:24:04 -0700226 /** This is the Intent action to install a shortcut in the launcher. */
227 private static final String ACTION_INSTALL_SHORTCUT =
228 "com.android.launcher.action.INSTALL_SHORTCUT";
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700229
230 @SuppressWarnings("deprecation")
231 private static final String LEGACY_AUTHORITY = android.provider.Contacts.AUTHORITY;
232
Paul Soulose0055662014-07-10 16:33:08 -0700233 private static final String MIMETYPE_GPLUS_PROFILE =
234 "vnd.android.cursor.item/vnd.googleplus.profile";
Paul Soulos6a2a1a72014-10-10 15:52:56 -0700235 private static final String GPLUS_PROFILE_DATA_5_ADD_TO_CIRCLE = "addtocircle";
236 private static final String GPLUS_PROFILE_DATA_5_VIEW_PROFILE = "view";
Paul Soulose0055662014-07-10 16:33:08 -0700237 private static final String MIMETYPE_HANGOUTS =
238 "vnd.android.cursor.item/vnd.googleplus.profile.comm";
Paul Soulos6a2a1a72014-10-10 15:52:56 -0700239 private static final String HANGOUTS_DATA_5_VIDEO = "hangout";
240 private static final String HANGOUTS_DATA_5_MESSAGE = "conversation";
Paul Soulos48fc9122014-08-26 13:52:36 -0700241 private static final String CALL_ORIGIN_QUICK_CONTACTS_ACTIVITY =
242 "com.android.contacts.quickcontact.QuickContactActivity";
Paul Soulose0055662014-07-10 16:33:08 -0700243
Brian Attwell63176c92014-08-18 15:14:18 -0700244 /**
245 * The URI used to load the the Contact. Once the contact is loaded, use Contact#getLookupUri()
246 * instead of referencing this URI.
247 */
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700248 private Uri mLookupUri;
249 private String[] mExcludeMimes;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700250 private int mExtraMode;
Brian Attwell05287bf2015-02-25 22:24:04 -0800251 private String mExtraPrioritizedMimeType;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700252 private int mStatusBarColor;
253 private boolean mHasAlreadyBeenOpened;
Paul Soulos97f27802014-09-08 13:55:45 -0700254 private boolean mOnlyOnePhoneNumber;
255 private boolean mOnlyOneEmail;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700256
Brian Attwell02eaf112014-08-27 21:03:42 -0700257 private QuickContactImageView mPhotoView;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700258 private ExpandingEntryCardView mContactCard;
Brian Attwell60953692014-07-11 17:18:46 -0700259 private ExpandingEntryCardView mNoContactDetailsCard;
Paul Soulosb3054e52014-06-05 16:46:02 -0700260 private ExpandingEntryCardView mRecentCard;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700261 private ExpandingEntryCardView mAboutCard;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700262
263 // Suggestion card.
Tingting Wang18906c02015-10-16 14:53:10 -0700264 private CardView mCollapsedSuggestionCardView;
265 private CardView mExpandSuggestionCardView;
266 private View mCollapasedSuggestionHeader;
267 private TextView mCollapsedSuggestionCardTitle;
268 private TextView mExpandSuggestionCardTitle;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700269 private ImageView mSuggestionSummaryPhoto;
270 private TextView mSuggestionForName;
Wenyi Wang53684892015-10-23 11:11:16 -0700271 private TextView mSuggestionContactsNumber;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700272 private LinearLayout mSuggestionList;
Tingting Wang18906c02015-10-16 14:53:10 -0700273 private Button mSuggestionsCancelButton;
Tingting Wang5585c6e2015-10-14 11:19:58 -0700274 private Button mSuggestionsLinkButton;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700275 private boolean mIsSuggestionListCollapsed;
Tingting Wang18906c02015-10-16 14:53:10 -0700276 private boolean mSuggestionsShouldAutoSelected = true;
277 private long mPreviousContactId = 0;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700278
Brian Attwellb7e43642014-06-02 14:33:04 -0700279 private MultiShrinkScroller mScroller;
Brian Attwelld28851f2014-06-10 13:25:07 -0700280 private SelectAccountDialogFragmentListener mSelectAccountFragmentListener;
Brian Attwell6bb01342014-08-20 23:16:00 -0700281 private AsyncTask<Void, Void, Cp2DataCardModel> mEntriesAndActionsTask;
Brian Attwell30e1ef12014-09-02 15:49:20 -0700282 private AsyncTask<Void, Void, Void> mRecentDataTask;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700283
284 private AggregationSuggestionEngine mAggregationSuggestionEngine;
285 private List<Suggestion> mSuggestions;
286
287 private TreeSet<Long> mSelectedAggregationIds = new TreeSet<>();
Brian Attwell6bb01342014-08-20 23:16:00 -0700288 /**
289 * The last copy of Cp2DataCardModel that was passed to {@link #populateContactAndAboutCard}.
290 */
291 private Cp2DataCardModel mCachedCp2DataCardModel;
Brian Attwellf1076dc2014-08-12 21:28:21 -0700292 /**
293 * This scrim's opacity is controlled in two different ways. 1) Before the initial entrance
294 * animation finishes, the opacity is animated by a value animator. This is designed to
295 * distract the user from the length of the initial loading time. 2) After the initial
296 * entrance animation, the opacity is directly related to scroll position.
297 */
Brian Attwell9b889e62014-06-23 18:25:32 -0700298 private ColorDrawable mWindowScrim;
Brian Attwellf1076dc2014-08-12 21:28:21 -0700299 private boolean mIsEntranceAnimationFinished;
Brian Attwella3859ed2014-07-15 13:51:55 -0700300 private MaterialColorMapUtils mMaterialColorMapUtils;
Brian Attwell8477eaf2014-06-18 15:39:50 -0700301 private boolean mIsExitAnimationInProgress;
Brian Attwell8571dd32014-06-23 23:29:10 -0700302 private boolean mHasComputedThemeColor;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700303
Paul Soulosa42ef762014-08-20 10:26:10 -0700304 /**
305 * Used to stop the ExpandingEntry cards from adjusting between an entry click and the intent
306 * being launched.
307 */
308 private boolean mHasIntentLaunched;
309
Yorke Lee6cb6f6f2013-09-26 16:26:03 -0700310 private Contact mContactData;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700311 private ContactLoader mContactLoader;
Brian Attwell9b889e62014-06-23 18:25:32 -0700312 private PorterDuffColorFilter mColorFilter;
Brian Attwell8e29faf2015-01-21 10:22:40 -0800313 private int mColorFilterColor;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700314
Josh Gargus9758a922012-03-08 17:12:42 -0800315 private final ImageViewDrawableSetter mPhotoSetter = new ImageViewDrawableSetter();
316
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700317 /**
Paul Souloscb4fcc72014-07-15 14:08:50 -0700318 * {@link #LEADING_MIMETYPES} is used to sort MIME-types.
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700319 *
320 * <p>The MIME-types in {@link #LEADING_MIMETYPES} appear in the front of the dialog,
321 * in the order specified here.</p>
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700322 */
323 private static final List<String> LEADING_MIMETYPES = Lists.newArrayList(
Paul Souloscb4fcc72014-07-15 14:08:50 -0700324 Phone.CONTENT_ITEM_TYPE, SipAddress.CONTENT_ITEM_TYPE, Email.CONTENT_ITEM_TYPE,
Paul Soulos405ae402014-07-14 16:16:36 -0700325 StructuredPostal.CONTENT_ITEM_TYPE);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700326
Paul Soulosa153dba2014-08-06 13:44:57 -0700327 private static final List<String> SORTED_ABOUT_CARD_MIMETYPES = Lists.newArrayList(
328 Nickname.CONTENT_ITEM_TYPE,
329 // Phonetic name is inserted after nickname if it is available.
330 // No mimetype for phonetic name exists.
331 Website.CONTENT_ITEM_TYPE,
332 Organization.CONTENT_ITEM_TYPE,
333 Event.CONTENT_ITEM_TYPE,
334 Relation.CONTENT_ITEM_TYPE,
335 Im.CONTENT_ITEM_TYPE,
336 GroupMembership.CONTENT_ITEM_TYPE,
337 Identity.CONTENT_ITEM_TYPE,
338 Note.CONTENT_ITEM_TYPE);
Paul Souloseb64a4b2014-07-07 17:03:27 -0700339
Brian Attwellc62cc792014-10-02 12:35:07 -0700340 private static final BidiFormatter sBidiFormatter = BidiFormatter.getInstance();
341
Paul Soulosb3054e52014-06-05 16:46:02 -0700342 /** Id for the background contact loader */
343 private static final int LOADER_CONTACT_ID = 0;
344
Paul Soulosab840442014-06-17 14:08:40 -0700345 private static final String KEY_LOADER_EXTRA_PHONES =
346 QuickContactActivity.class.getCanonicalName() + ".KEY_LOADER_EXTRA_PHONES";
347
Paul Soulosb3054e52014-06-05 16:46:02 -0700348 /** Id for the background Sms Loader */
349 private static final int LOADER_SMS_ID = 1;
Paul Soulosb3054e52014-06-05 16:46:02 -0700350 private static final int MAX_SMS_RETRIEVE = 3;
Paul Soulosab840442014-06-17 14:08:40 -0700351
352 /** Id for the back Calendar Loader */
Paul Soulos899aa212014-06-11 12:04:43 -0700353 private static final int LOADER_CALENDAR_ID = 2;
Paul Soulosab840442014-06-17 14:08:40 -0700354 private static final String KEY_LOADER_EXTRA_EMAILS =
355 QuickContactActivity.class.getCanonicalName() + ".KEY_LOADER_EXTRA_EMAILS";
Paul Soulos899aa212014-06-11 12:04:43 -0700356 private static final int MAX_PAST_CALENDAR_RETRIEVE = 3;
357 private static final int MAX_FUTURE_CALENDAR_RETRIEVE = 3;
358 private static final long PAST_MILLISECOND_TO_SEARCH_LOCAL_CALENDAR =
Paul Soulos40d7a652014-09-03 13:54:11 -0700359 1L * 24L * 60L * 60L * 1000L /* 1 day */;
Paul Soulos899aa212014-06-11 12:04:43 -0700360 private static final long FUTURE_MILLISECOND_TO_SEARCH_LOCAL_CALENDAR =
Paul Soulos40d7a652014-09-03 13:54:11 -0700361 7L * 24L * 60L * 60L * 1000L /* 7 days */;
Paul Soulosb3054e52014-06-05 16:46:02 -0700362
Paul Soulosab840442014-06-17 14:08:40 -0700363 /** Id for the background Call Log Loader */
364 private static final int LOADER_CALL_LOG_ID = 3;
365 private static final int MAX_CALL_LOG_RETRIEVE = 3;
Brian Attwell60953692014-07-11 17:18:46 -0700366 private static final int MIN_NUM_CONTACT_ENTRIES_SHOWN = 3;
367 private static final int MIN_NUM_COLLAPSED_RECENT_ENTRIES_SHOWN = 3;
368 private static final int CARD_ENTRY_ID_EDIT_CONTACT = -2;
Paul Soulosab840442014-06-17 14:08:40 -0700369
370
371 private static final int[] mRecentLoaderIds = new int[]{
372 LOADER_SMS_ID,
373 LOADER_CALENDAR_ID,
374 LOADER_CALL_LOG_ID};
Jay Shrauner12ac1e62014-12-08 15:57:19 -0800375 /**
376 * ConcurrentHashMap constructor params: 4 is initial table size, 0.9f is
377 * load factor before resizing, 1 means we only expect a single thread to
378 * write to the map so make only a single shard
379 */
380 private Map<Integer, List<ContactInteraction>> mRecentLoaderResults =
381 new ConcurrentHashMap<>(4, 0.9f, 1);
Paul Soulosb3054e52014-06-05 16:46:02 -0700382
Brian Attwelld28851f2014-06-10 13:25:07 -0700383 private static final String FRAGMENT_TAG_SELECT_ACCOUNT = "select_account_fragment";
Yorke Lee6cb6f6f2013-09-26 16:26:03 -0700384
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700385 final OnClickListener mEntryClickHandler = new OnClickListener() {
386 @Override
387 public void onClick(View v) {
Paul Soulos2ed2a732014-08-12 11:58:39 -0700388 final Object entryTagObject = v.getTag();
389 if (entryTagObject == null || !(entryTagObject instanceof EntryTag)) {
390 Log.w(TAG, "EntryTag was not used correctly");
391 return;
392 }
393 final EntryTag entryTag = (EntryTag) entryTagObject;
394 final Intent intent = entryTag.getIntent();
395 final int dataId = entryTag.getId();
396
Brian Attwell60953692014-07-11 17:18:46 -0700397 if (dataId == CARD_ENTRY_ID_EDIT_CONTACT) {
398 editContact();
399 return;
400 }
Paul Soulosea5e0b72014-07-08 18:09:44 -0700401
LIANGTAO GAO19c83e32015-03-12 13:32:04 -0700402 // Pass the touch point through the intent for use in the InCallUI
403 if (Intent.ACTION_CALL.equals(intent.getAction())) {
404 if (TouchPointManager.getInstance().hasValidPoint()) {
405 Bundle extras = new Bundle();
406 extras.putParcelable(TouchPointManager.TOUCH_POINT,
407 TouchPointManager.getInstance().getPoint());
408 intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
409 }
410 }
411
412 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
413
414 mHasIntentLaunched = true;
415 try {
Tingting Wang8c434e52016-01-29 16:45:09 -0800416 ImplicitIntentsUtil.startActivityInAppIfPossible(QuickContactActivity.this, intent);
LIANGTAO GAO19c83e32015-03-12 13:32:04 -0700417 } catch (SecurityException ex) {
418 Toast.makeText(QuickContactActivity.this, R.string.missing_app,
419 Toast.LENGTH_SHORT).show();
420 Log.e(TAG, "QuickContacts does not have permission to launch "
421 + intent);
422 } catch (ActivityNotFoundException ex) {
423 Toast.makeText(QuickContactActivity.this, R.string.missing_app,
424 Toast.LENGTH_SHORT).show();
425 }
426
Paul Soulosea5e0b72014-07-08 18:09:44 -0700427 // Default to USAGE_TYPE_CALL. Usage is summed among all types for sorting each data id
428 // so the exact usage type is not necessary in all cases
429 String usageType = DataUsageFeedback.USAGE_TYPE_CALL;
430
Paul Soulos20bdf9d2014-07-28 14:31:54 -0700431 final Uri intentUri = intent.getData();
432 if ((intentUri != null && intentUri.getScheme() != null &&
Jay Shrauner1cd88e32014-09-05 15:37:55 -0700433 intentUri.getScheme().equals(ContactsUtils.SCHEME_SMSTO)) ||
Paul Soulosea5e0b72014-07-08 18:09:44 -0700434 (intent.getType() != null && intent.getType().equals(MIMETYPE_SMS))) {
435 usageType = DataUsageFeedback.USAGE_TYPE_SHORT_TEXT;
436 }
437
438 // Data IDs start at 1 so anything less is invalid
439 if (dataId > 0) {
Paul Soulos20bdf9d2014-07-28 14:31:54 -0700440 final Uri dataUsageUri = DataUsageFeedback.FEEDBACK_URI.buildUpon()
Paul Soulosea5e0b72014-07-08 18:09:44 -0700441 .appendPath(String.valueOf(dataId))
442 .appendQueryParameter(DataUsageFeedback.USAGE_TYPE, usageType)
443 .build();
Jay Shrauner98818822015-07-10 12:54:10 -0700444 try {
445 final boolean successful = getContentResolver().update(
446 dataUsageUri, new ContentValues(), null, null) > 0;
447 if (!successful) {
448 Log.w(TAG, "DataUsageFeedback increment failed");
449 }
450 } catch (SecurityException ex) {
451 Log.w(TAG, "DataUsageFeedback increment failed", ex);
Paul Soulosea5e0b72014-07-08 18:09:44 -0700452 }
453 } else {
454 Log.w(TAG, "Invalid Data ID");
455 }
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700456 }
457 };
458
Brian Attwelle8ce6ee2014-06-27 18:26:32 -0700459 final ExpandingEntryCardViewListener mExpandingEntryCardViewListener
460 = new ExpandingEntryCardViewListener() {
461 @Override
462 public void onCollapse(int heightDelta) {
463 mScroller.prepareForShrinkingScrollChild(heightDelta);
464 }
Paul Soulos0cda9ae2014-07-23 11:27:28 -0700465
466 @Override
Brian Attwell245d3d22015-01-21 09:50:08 -0800467 public void onExpand() {
468 mScroller.setDisableTouchesForSuppressLayout(/* areTouchesDisabled = */ true);
469 }
470
471 @Override
472 public void onExpandDone() {
473 mScroller.setDisableTouchesForSuppressLayout(/* areTouchesDisabled = */ false);
Paul Soulos0cda9ae2014-07-23 11:27:28 -0700474 }
Brian Attwelle8ce6ee2014-06-27 18:26:32 -0700475 };
476
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700477 @Override
478 public void onAggregationSuggestionChange() {
Wenyi Wang1d86a672016-01-28 17:37:10 -0800479 if (mAggregationSuggestionEngine == null) {
480 return;
481 }
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700482 mSuggestions = mAggregationSuggestionEngine.getSuggestions();
Tingting Wang18906c02015-10-16 14:53:10 -0700483 mCollapsedSuggestionCardView.setVisibility(View.GONE);
484 mExpandSuggestionCardView.setVisibility(View.GONE);
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700485 mSuggestionList.removeAllViews();
486
Wenyi Wang96efbb82015-10-26 11:36:02 -0700487 if (mContactData == null) {
488 return;
489 }
490
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700491 final String suggestionForName = mContactData.getDisplayName();
492 final int suggestionNumber = mSuggestions.size();
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700493
Tingting Wange29b6942015-09-30 15:21:33 -0700494 if (suggestionNumber <= 0) {
495 mSelectedAggregationIds.clear();
496 return;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700497 }
Tingting Wange29b6942015-09-30 15:21:33 -0700498
Wenyi Wang53684892015-10-23 11:11:16 -0700499 ContactPhotoManager.DefaultImageRequest
500 request = new ContactPhotoManager.DefaultImageRequest(
501 suggestionForName, mContactData.getLookupKey(), ContactPhotoManager.TYPE_DEFAULT,
502 /* isCircular */ true );
503 final long photoId = mContactData.getPhotoId();
Tingting Wang18906c02015-10-16 14:53:10 -0700504 final byte[] photoBytes = mContactData.getThumbnailPhotoBinaryData();
505 if (photoBytes != null) {
Wenyi Wang53684892015-10-23 11:11:16 -0700506 ContactPhotoManager.getInstance(this).loadThumbnail(mSuggestionSummaryPhoto, photoId,
Wenyi Wang5cf746f2015-10-23 18:27:11 -0700507 /* darkTheme */ false , /* isCircular */ true , request);
Tingting Wange29b6942015-09-30 15:21:33 -0700508 } else {
Wenyi Wang53684892015-10-23 11:11:16 -0700509 ContactPhotoManager.DEFAULT_AVATAR.applyDefaultImage(mSuggestionSummaryPhoto,
510 -1, false, request);
Tingting Wange29b6942015-09-30 15:21:33 -0700511 }
512
Tingting Wang18906c02015-10-16 14:53:10 -0700513 final String suggestionTitle = getResources().getQuantityString(
514 R.plurals.quickcontact_suggestion_card_title, suggestionNumber, suggestionNumber);
515 mCollapsedSuggestionCardTitle.setText(suggestionTitle);
516 mExpandSuggestionCardTitle.setText(suggestionTitle);
517
Tingting Wange29b6942015-09-30 15:21:33 -0700518 mSuggestionForName.setText(suggestionForName);
Wenyi Wang53684892015-10-23 11:11:16 -0700519 final int linkedContactsNumber = mContactData.getRawContacts().size();
520 final String contactsInfo;
Wenyi Wang96efbb82015-10-26 11:36:02 -0700521 final String accountName = mContactData.getRawContacts().get(0).getAccountName();
522 if (linkedContactsNumber == 1 && accountName == null) {
523 mSuggestionContactsNumber.setVisibility(View.INVISIBLE);
524 }
525 if (linkedContactsNumber == 1 && accountName != null) {
Wenyi Wang53684892015-10-23 11:11:16 -0700526 contactsInfo = getResources().getString(R.string.contact_from_account_name,
Wenyi Wang96efbb82015-10-26 11:36:02 -0700527 accountName);
Wenyi Wang53684892015-10-23 11:11:16 -0700528 } else {
529 contactsInfo = getResources().getString(
530 R.string.quickcontact_contacts_number, linkedContactsNumber);
531 }
532 mSuggestionContactsNumber.setText(contactsInfo);
Tingting Wange29b6942015-09-30 15:21:33 -0700533
534 final Set<Long> suggestionContactIds = new HashSet<>();
535 for (Suggestion suggestion : mSuggestions) {
536 mSuggestionList.addView(inflateSuggestionListView(suggestion));
537 suggestionContactIds.add(suggestion.contactId);
538 }
539
Tingting Wang18906c02015-10-16 14:53:10 -0700540 if (mIsSuggestionListCollapsed) {
541 collapseSuggestionList();
542 } else {
543 expandSuggestionList();
544 }
545
Tingting Wange29b6942015-09-30 15:21:33 -0700546 // Remove contact Ids that are not suggestions.
547 final Set<Long> selectedSuggestionIds = com.google.common.collect.Sets.intersection(
548 mSelectedAggregationIds, suggestionContactIds);
549 mSelectedAggregationIds = new TreeSet<>(selectedSuggestionIds);
Wenyi Wang96efbb82015-10-26 11:36:02 -0700550 if (!mSelectedAggregationIds.isEmpty()) {
551 enableLinkButton();
552 }
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700553 }
554
555 private void collapseSuggestionList() {
Tingting Wang18906c02015-10-16 14:53:10 -0700556 mCollapsedSuggestionCardView.setVisibility(View.VISIBLE);
557 mExpandSuggestionCardView.setVisibility(View.GONE);
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700558 mIsSuggestionListCollapsed = true;
559 }
560
561 private void expandSuggestionList() {
Tingting Wang18906c02015-10-16 14:53:10 -0700562 mCollapsedSuggestionCardView.setVisibility(View.GONE);
563 mExpandSuggestionCardView.setVisibility(View.VISIBLE);
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700564 mIsSuggestionListCollapsed = false;
565 }
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700566
Tingting Wang18906c02015-10-16 14:53:10 -0700567 private View inflateSuggestionListView(final Suggestion suggestion) {
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700568 final LayoutInflater layoutInflater = LayoutInflater.from(this);
569 final View suggestionView = layoutInflater.inflate(
570 R.layout.quickcontact_suggestion_contact_item, null);
571
Wenyi Wang53684892015-10-23 11:11:16 -0700572 ContactPhotoManager.DefaultImageRequest
573 request = new ContactPhotoManager.DefaultImageRequest(
574 suggestion.name, suggestion.lookupKey, ContactPhotoManager.TYPE_DEFAULT, /*
575 isCircular */ true);
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700576 final ImageView photo = (ImageView) suggestionView.findViewById(
577 R.id.aggregation_suggestion_photo);
578 if (suggestion.photo != null) {
Wenyi Wang53684892015-10-23 11:11:16 -0700579 ContactPhotoManager.getInstance(this).loadThumbnail(photo, suggestion.photoId,
Wenyi Wang5cf746f2015-10-23 18:27:11 -0700580 /* darkTheme */ false, /* isCircular */ true, request);
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700581 } else {
Wenyi Wang53684892015-10-23 11:11:16 -0700582 ContactPhotoManager.DEFAULT_AVATAR.applyDefaultImage(photo, -1, false, request);
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700583 }
584
585 final TextView name = (TextView) suggestionView.findViewById(R.id.aggregation_suggestion_name);
586 name.setText(suggestion.name);
587
Tingting Wang18906c02015-10-16 14:53:10 -0700588 final TextView accountNameView = (TextView) suggestionView.findViewById(
589 R.id.aggregation_suggestion_account_name);
590 final String accountName = suggestion.rawContacts.get(0).accountName;
591 if (!TextUtils.isEmpty(accountName)) {
Wenyi Wang53684892015-10-23 11:11:16 -0700592 accountNameView.setText(
593 getResources().getString(R.string.contact_from_account_name, accountName));
Tingting Wang18906c02015-10-16 14:53:10 -0700594 } else {
Wenyi Wang96efbb82015-10-26 11:36:02 -0700595 accountNameView.setVisibility(View.INVISIBLE);
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700596 }
597
598 final CheckBox checkbox = (CheckBox) suggestionView.findViewById(R.id.suggestion_checkbox);
Wenyi Wang8d02b162016-01-10 12:43:36 -0800599 final int[][] stateSet = new int[][] {
600 new int[] { android.R.attr.state_checked },
601 new int[] { -android.R.attr.state_checked }
602 };
603 final int[] colors = new int[] { mColorFilterColor, mColorFilterColor };
guanxiongliu051315f2016-02-22 15:51:00 -0800604 if (suggestion != null && suggestion.name != null) {
605 checkbox.setContentDescription(suggestion.name + " " +
606 getResources().getString(R.string.contact_from_account_name, accountName));
607 }
Wenyi Wang8d02b162016-01-10 12:43:36 -0800608 checkbox.setButtonTintList(new ColorStateList(stateSet, colors));
Tingting Wang18906c02015-10-16 14:53:10 -0700609 checkbox.setChecked(mSuggestionsShouldAutoSelected ||
610 mSelectedAggregationIds.contains(suggestion.contactId));
611 if (checkbox.isChecked()) {
612 mSelectedAggregationIds.add(suggestion.contactId);
613 }
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700614 checkbox.setTag(suggestion.contactId);
615 checkbox.setOnClickListener(new OnClickListener() {
616 @Override
617 public void onClick(View v) {
618 final CheckBox checkBox = (CheckBox) v;
619 final Long contactId = (Long) checkBox.getTag();
Wenyi Wang53684892015-10-23 11:11:16 -0700620 if (mSelectedAggregationIds.contains(mContactData.getId())) {
621 mSelectedAggregationIds.remove(mContactData.getId());
622 }
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700623 if (checkBox.isChecked()) {
624 mSelectedAggregationIds.add(contactId);
Wenyi Wang53684892015-10-23 11:11:16 -0700625 if (mSelectedAggregationIds.size() >= 1) {
626 enableLinkButton();
627 }
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700628 } else {
629 mSelectedAggregationIds.remove(contactId);
Tingting Wang18906c02015-10-16 14:53:10 -0700630 mSuggestionsShouldAutoSelected = false;
Wenyi Wang53684892015-10-23 11:11:16 -0700631 if (mSelectedAggregationIds.isEmpty()) {
632 disableLinkButton();
633 }
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700634 }
635 }
636 });
Tingting Wang18906c02015-10-16 14:53:10 -0700637
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700638 return suggestionView;
639 }
640
Wenyi Wang53684892015-10-23 11:11:16 -0700641 private void enableLinkButton() {
642 mSuggestionsLinkButton.setClickable(true);
Wenyi Wang8d02b162016-01-10 12:43:36 -0800643 mSuggestionsLinkButton.getBackground().setColorFilter(mColorFilter);
Wenyi Wang56dfa582015-11-17 17:42:44 -0800644 mSuggestionsLinkButton.setTextColor(
Wenyi Wang23d9b6e2015-11-19 15:09:06 -0800645 ContextCompat.getColor(this, android.R.color.white));
Wenyi Wang53684892015-10-23 11:11:16 -0700646 mSuggestionsLinkButton.setOnClickListener(new OnClickListener() {
647 @Override
648 public void onClick(View view) {
649 // Join selected contacts.
650 if (!mSelectedAggregationIds.contains(mContactData.getId())) {
651 mSelectedAggregationIds.add(mContactData.getId());
652 }
653 JoinContactsDialogFragment.start(
654 QuickContactActivity.this, mSelectedAggregationIds);
655 }
656 });
657 }
658
659 @Override
660 public void onContactsJoined() {
661 disableLinkButton();
662 }
663
664 private void disableLinkButton() {
665 mSuggestionsLinkButton.setClickable(false);
666 mSuggestionsLinkButton.getBackground().setColorFilter(
Wenyi Wang23d9b6e2015-11-19 15:09:06 -0800667 ContextCompat.getColor(this, R.color.disabled_button_background),
Wenyi Wang33f60d32015-10-24 14:44:06 -0700668 PorterDuff.Mode.SRC_ATOP);
Wenyi Wang53684892015-10-23 11:11:16 -0700669 mSuggestionsLinkButton.setTextColor(
Wenyi Wang23d9b6e2015-11-19 15:09:06 -0800670 ContextCompat.getColor(this, R.color.disabled_button_text));
Wenyi Wang53684892015-10-23 11:11:16 -0700671 }
672
Paul Soulos97f27802014-09-08 13:55:45 -0700673 private interface ContextMenuIds {
674 static final int COPY_TEXT = 0;
675 static final int CLEAR_DEFAULT = 1;
676 static final int SET_DEFAULT = 2;
677 }
678
Paul Soulos2a4207f2014-07-31 17:09:05 -0700679 private final OnCreateContextMenuListener mEntryContextMenuListener =
680 new OnCreateContextMenuListener() {
681 @Override
682 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
683 if (menuInfo == null) {
684 return;
685 }
Paul Soulos97f27802014-09-08 13:55:45 -0700686 final EntryContextMenuInfo info = (EntryContextMenuInfo) menuInfo;
Paul Soulos2a4207f2014-07-31 17:09:05 -0700687 menu.setHeaderTitle(info.getCopyText());
Paul Soulos97f27802014-09-08 13:55:45 -0700688 menu.add(ContextMenu.NONE, ContextMenuIds.COPY_TEXT,
689 ContextMenu.NONE, getString(R.string.copy_text));
690
691 // Don't allow setting or clearing of defaults for non-editable contacts
692 if (!isContactEditable()) {
693 return;
694 }
695
696 final String selectedMimeType = info.getMimeType();
697
698 // Defaults to true will only enable the detail to be copied to the clipboard.
699 boolean onlyOneOfMimeType = true;
700
701 // Only allow primary support for Phone and Email content types
702 if (Phone.CONTENT_ITEM_TYPE.equals(selectedMimeType)) {
703 onlyOneOfMimeType = mOnlyOnePhoneNumber;
704 } else if (Email.CONTENT_ITEM_TYPE.equals(selectedMimeType)) {
705 onlyOneOfMimeType = mOnlyOneEmail;
706 }
707
708 // Checking for previously set default
709 if (info.isSuperPrimary()) {
710 menu.add(ContextMenu.NONE, ContextMenuIds.CLEAR_DEFAULT,
711 ContextMenu.NONE, getString(R.string.clear_default));
712 } else if (!onlyOneOfMimeType) {
713 menu.add(ContextMenu.NONE, ContextMenuIds.SET_DEFAULT,
714 ContextMenu.NONE, getString(R.string.set_default));
715 }
Paul Soulos2a4207f2014-07-31 17:09:05 -0700716 }
717 };
718
719 @Override
720 public boolean onContextItemSelected(MenuItem item) {
721 EntryContextMenuInfo menuInfo;
722 try {
723 menuInfo = (EntryContextMenuInfo) item.getMenuInfo();
724 } catch (ClassCastException e) {
725 Log.e(TAG, "bad menuInfo", e);
726 return false;
727 }
728
Paul Soulos97f27802014-09-08 13:55:45 -0700729 switch (item.getItemId()) {
730 case ContextMenuIds.COPY_TEXT:
731 ClipboardUtils.copyText(this, menuInfo.getCopyLabel(), menuInfo.getCopyText(),
732 true);
733 return true;
734 case ContextMenuIds.SET_DEFAULT:
735 final Intent setIntent = ContactSaveService.createSetSuperPrimaryIntent(this,
736 menuInfo.getId());
737 this.startService(setIntent);
738 return true;
739 case ContextMenuIds.CLEAR_DEFAULT:
740 final Intent clearIntent = ContactSaveService.createClearPrimaryIntent(this,
741 menuInfo.getId());
742 this.startService(clearIntent);
743 return true;
744 default:
745 throw new IllegalArgumentException("Unknown menu option " + item.getItemId());
746 }
Paul Soulos2a4207f2014-07-31 17:09:05 -0700747 }
748
Brian Attwelld28851f2014-06-10 13:25:07 -0700749 /**
750 * Headless fragment used to handle account selection callbacks invoked from
751 * {@link DirectoryContactUtil}.
752 */
753 public static class SelectAccountDialogFragmentListener extends Fragment
754 implements SelectAccountDialogFragment.Listener {
755
756 private QuickContactActivity mQuickContactActivity;
757
758 public SelectAccountDialogFragmentListener() {}
759
760 @Override
761 public void onAccountChosen(AccountWithDataSet account, Bundle extraArgs) {
762 DirectoryContactUtil.createCopy(mQuickContactActivity.mContactData.getContentValues(),
763 account, mQuickContactActivity);
764 }
765
766 @Override
767 public void onAccountSelectorCancelled() {}
768
769 /**
770 * Set the parent activity. Since rotation can cause this fragment to be used across
771 * more than one activity instance, we need to explicitly set this value instead
772 * of making this class non-static.
773 */
774 public void setQuickContactActivity(QuickContactActivity quickContactActivity) {
775 mQuickContactActivity = quickContactActivity;
776 }
777 }
778
Brian Attwellb7e43642014-06-02 14:33:04 -0700779 final MultiShrinkScrollerListener mMultiShrinkScrollerListener
780 = new MultiShrinkScrollerListener() {
781 @Override
782 public void onScrolledOffBottom() {
Brian Attwellf1076dc2014-08-12 21:28:21 -0700783 finish();
Brian Attwellb7e43642014-06-02 14:33:04 -0700784 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700785
786 @Override
787 public void onEnterFullscreen() {
788 updateStatusBarColor();
789 }
790
791 @Override
792 public void onExitFullscreen() {
793 updateStatusBarColor();
794 }
Brian Attwell8477eaf2014-06-18 15:39:50 -0700795
796 @Override
797 public void onStartScrollOffBottom() {
Brian Attwell8477eaf2014-06-18 15:39:50 -0700798 mIsExitAnimationInProgress = true;
799 }
Brian Attwell8477eaf2014-06-18 15:39:50 -0700800
Brian Attwell8477eaf2014-06-18 15:39:50 -0700801 @Override
Brian Attwellf1076dc2014-08-12 21:28:21 -0700802 public void onEntranceAnimationDone() {
803 mIsEntranceAnimationFinished = true;
804 }
805
806 @Override
807 public void onTransparentViewHeightChange(float ratio) {
808 if (mIsEntranceAnimationFinished) {
809 mWindowScrim.setAlpha((int) (0xFF * ratio));
Brian Attwell8477eaf2014-06-18 15:39:50 -0700810 }
Brian Attwell8477eaf2014-06-18 15:39:50 -0700811 }
Brian Attwellb7e43642014-06-02 14:33:04 -0700812 };
813
Paul Souloseb64a4b2014-07-07 17:03:27 -0700814
815 /**
816 * Data items are compared to the same mimetype based off of three qualities:
817 * 1. Super primary
818 * 2. Primary
819 * 3. Times used
820 */
821 private final Comparator<DataItem> mWithinMimeTypeDataItemComparator =
822 new Comparator<DataItem>() {
823 @Override
824 public int compare(DataItem lhs, DataItem rhs) {
825 if (!lhs.getMimeType().equals(rhs.getMimeType())) {
826 Log.wtf(TAG, "Comparing DataItems with different mimetypes lhs.getMimeType(): " +
827 lhs.getMimeType() + " rhs.getMimeType(): " + rhs.getMimeType());
828 return 0;
829 }
830
831 if (lhs.isSuperPrimary()) {
832 return -1;
833 } else if (rhs.isSuperPrimary()) {
834 return 1;
835 } else if (lhs.isPrimary() && !rhs.isPrimary()) {
836 return -1;
837 } else if (!lhs.isPrimary() && rhs.isPrimary()) {
838 return 1;
839 } else {
840 final int lhsTimesUsed =
841 lhs.getTimesUsed() == null ? 0 : lhs.getTimesUsed();
842 final int rhsTimesUsed =
843 rhs.getTimesUsed() == null ? 0 : rhs.getTimesUsed();
844
845 return rhsTimesUsed - lhsTimesUsed;
846 }
847 }
848 };
849
Paul Souloscb4fcc72014-07-15 14:08:50 -0700850 /**
851 * Sorts among different mimetypes based off:
Brian Attwell05287bf2015-02-25 22:24:04 -0800852 * 1. Whether one of the mimetypes is the prioritized mimetype
853 * 2. Number of times used
854 * 3. Last time used
855 * 4. Statically defined
Paul Souloscb4fcc72014-07-15 14:08:50 -0700856 */
Paul Souloseb64a4b2014-07-07 17:03:27 -0700857 private final Comparator<List<DataItem>> mAmongstMimeTypeDataItemComparator =
858 new Comparator<List<DataItem>> () {
859 @Override
860 public int compare(List<DataItem> lhsList, List<DataItem> rhsList) {
Brian Attwell05287bf2015-02-25 22:24:04 -0800861 final DataItem lhs = lhsList.get(0);
862 final DataItem rhs = rhsList.get(0);
863 final String lhsMimeType = lhs.getMimeType();
864 final String rhsMimeType = rhs.getMimeType();
865
866 // 1. Whether one of the mimetypes is the prioritized mimetype
867 if (!TextUtils.isEmpty(mExtraPrioritizedMimeType) && !lhsMimeType.equals(rhsMimeType)) {
868 if (rhsMimeType.equals(mExtraPrioritizedMimeType)) {
869 return 1;
870 }
871 if (lhsMimeType.equals(mExtraPrioritizedMimeType)) {
872 return -1;
873 }
874 }
875
876 // 2. Number of times used
Paul Souloseb64a4b2014-07-07 17:03:27 -0700877 final int lhsTimesUsed = lhs.getTimesUsed() == null ? 0 : lhs.getTimesUsed();
878 final int rhsTimesUsed = rhs.getTimesUsed() == null ? 0 : rhs.getTimesUsed();
879 final int timesUsedDifference = rhsTimesUsed - lhsTimesUsed;
880 if (timesUsedDifference != 0) {
881 return timesUsedDifference;
882 }
883
Brian Attwell05287bf2015-02-25 22:24:04 -0800884 // 3. Last time used
Paul Souloseb64a4b2014-07-07 17:03:27 -0700885 final long lhsLastTimeUsed =
886 lhs.getLastTimeUsed() == null ? 0 : lhs.getLastTimeUsed();
887 final long rhsLastTimeUsed =
888 rhs.getLastTimeUsed() == null ? 0 : rhs.getLastTimeUsed();
889 final long lastTimeUsedDifference = rhsLastTimeUsed - lhsLastTimeUsed;
890 if (lastTimeUsedDifference > 0) {
891 return 1;
892 } else if (lastTimeUsedDifference < 0) {
893 return -1;
894 }
895
Brian Attwell05287bf2015-02-25 22:24:04 -0800896 // 4. Resort to a statically defined mimetype order.
897 if (!lhsMimeType.equals(rhsMimeType)) {
898 for (String mimeType : LEADING_MIMETYPES) {
899 if (lhsMimeType.equals(mimeType)) {
900 return -1;
901 } else if (rhsMimeType.equals(mimeType)) {
902 return 1;
903 }
Paul Souloseb64a4b2014-07-07 17:03:27 -0700904 }
905 }
Paul Souloseb64a4b2014-07-07 17:03:27 -0700906 return 0;
907 }
908 };
909
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700910 @Override
Nancy Chenf619e432014-08-18 20:15:12 -0700911 public boolean dispatchTouchEvent(MotionEvent ev) {
912 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
913 TouchPointManager.getInstance().setPoint((int) ev.getRawX(), (int) ev.getRawY());
914 }
915 return super.dispatchTouchEvent(ev);
916 }
917
918 @Override
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700919 protected void onCreate(Bundle savedInstanceState) {
920 Trace.beginSection("onCreate()");
921 super.onCreate(savedInstanceState);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700922
Walter Jangf6236372016-02-22 21:24:22 +0000923 if (RequestPermissionsActivity.startPermissionActivity(this) ||
924 RequestDesiredPermissionsActivity.startPermissionActivity(this)) {
Brian Attwellbdd32642015-05-08 17:03:15 -0700925 return;
926 }
927
Walter Jang1bb6c292016-02-29 11:46:03 -0800928 final int previousScreenType = getIntent().getIntExtra
929 (EXTRA_PREVIOUS_SCREEN_TYPE, ScreenType.UNKNOWN);
Walter Jang62a20742016-02-24 17:04:12 -0800930 Logger.logScreenView(this, ScreenType.QUICK_CONTACT, previousScreenType);
931
Wenyi Wang4c3d3e22015-12-17 14:30:02 -0800932 if (CompatUtils.isLollipopCompatible()) {
933 getWindow().setStatusBarColor(Color.TRANSPARENT);
934 }
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700935
Brian Attwelld28851f2014-06-10 13:25:07 -0700936 processIntent(getIntent());
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700937
Daisuke Miyakawa10d7df72011-07-29 16:11:05 -0700938 // Show QuickContact in front of soft input
939 getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
940 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
941
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700942 setContentView(R.layout.quickcontact_activity);
943
Brian Attwella3859ed2014-07-15 13:51:55 -0700944 mMaterialColorMapUtils = new MaterialColorMapUtils(getResources());
945
Paul Soulos0cda9ae2014-07-23 11:27:28 -0700946 mScroller = (MultiShrinkScroller) findViewById(R.id.multiscroller);
947
Paul Souloseb64a4b2014-07-07 17:03:27 -0700948 mContactCard = (ExpandingEntryCardView) findViewById(R.id.communication_card);
Brian Attwell60953692014-07-11 17:18:46 -0700949 mNoContactDetailsCard = (ExpandingEntryCardView) findViewById(R.id.no_contact_data_card);
Paul Soulosb3054e52014-06-05 16:46:02 -0700950 mRecentCard = (ExpandingEntryCardView) findViewById(R.id.recent_card);
Paul Souloseb64a4b2014-07-07 17:03:27 -0700951 mAboutCard = (ExpandingEntryCardView) findViewById(R.id.about_card);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700952
Tingting Wang18906c02015-10-16 14:53:10 -0700953 mCollapsedSuggestionCardView = (CardView) findViewById(R.id.collapsed_suggestion_card);
954 mExpandSuggestionCardView = (CardView) findViewById(R.id.expand_suggestion_card);
955 mCollapasedSuggestionHeader = findViewById(R.id.collapsed_suggestion_header);
956 mCollapsedSuggestionCardTitle = (TextView) findViewById(
957 R.id.collapsed_suggestion_card_title);
958 mExpandSuggestionCardTitle = (TextView) findViewById(R.id.expand_suggestion_card_title);
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700959 mSuggestionSummaryPhoto = (ImageView) findViewById(R.id.suggestion_icon);
960 mSuggestionForName = (TextView) findViewById(R.id.suggestion_for_name);
Wenyi Wang53684892015-10-23 11:11:16 -0700961 mSuggestionContactsNumber = (TextView) findViewById(R.id.suggestion_for_contacts_number);
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700962 mSuggestionList = (LinearLayout) findViewById(R.id.suggestion_list);
Tingting Wang18906c02015-10-16 14:53:10 -0700963 mSuggestionsCancelButton= (Button) findViewById(R.id.cancel_button);
Tingting Wang5585c6e2015-10-14 11:19:58 -0700964 mSuggestionsLinkButton = (Button) findViewById(R.id.link_button);
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700965 if (savedInstanceState != null) {
966 mIsSuggestionListCollapsed = savedInstanceState.getBoolean(
967 KEY_IS_SUGGESTION_LIST_COLLAPSED, true);
Tingting Wang18906c02015-10-16 14:53:10 -0700968 mPreviousContactId = savedInstanceState.getLong(KEY_PREVIOUS_CONTACT_ID);
969 mSuggestionsShouldAutoSelected = savedInstanceState.getBoolean(
970 KEY_SUGGESTIONS_AUTO_SELECTED, true);
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700971 mSelectedAggregationIds = (TreeSet<Long>)
972 savedInstanceState.getSerializable(KEY_SELECTED_SUGGESTION_CONTACTS);
973 } else {
974 mIsSuggestionListCollapsed = true;
975 mSelectedAggregationIds.clear();
976 }
Walter Jang28769772015-11-17 09:09:25 -0800977 if (mSelectedAggregationIds.isEmpty()) {
Wenyi Wang53684892015-10-23 11:11:16 -0700978 disableLinkButton();
Walter Jang28769772015-11-17 09:09:25 -0800979 } else {
980 enableLinkButton();
Wenyi Wang53684892015-10-23 11:11:16 -0700981 }
Tingting Wang18906c02015-10-16 14:53:10 -0700982 mCollapasedSuggestionHeader.setOnClickListener(new OnClickListener() {
983 @Override
984 public void onClick(View view) {
985 mCollapsedSuggestionCardView.setVisibility(View.GONE);
986 mExpandSuggestionCardView.setVisibility(View.VISIBLE);
987 mIsSuggestionListCollapsed = false;
guanxiongliubcae18d2016-02-26 11:04:19 -0800988 mExpandSuggestionCardTitle.requestFocus();
989 mExpandSuggestionCardTitle.sendAccessibilityEvent(
990 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
Tingting Wang18906c02015-10-16 14:53:10 -0700991 }
992 });
993
994 mSuggestionsCancelButton.setOnClickListener(new OnClickListener() {
995 @Override
996 public void onClick(View view) {
997 mCollapsedSuggestionCardView.setVisibility(View.VISIBLE);
998 mExpandSuggestionCardView.setVisibility(View.GONE);
999 mIsSuggestionListCollapsed = true;
1000 }
1001 });
1002
Brian Attwell60953692014-07-11 17:18:46 -07001003 mNoContactDetailsCard.setOnClickListener(mEntryClickHandler);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001004 mContactCard.setOnClickListener(mEntryClickHandler);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001005 mContactCard.setExpandButtonText(
Paul Soulos23889ba2014-06-10 14:55:32 -07001006 getResources().getString(R.string.expanding_entry_card_view_see_all));
Paul Soulos2a4207f2014-07-31 17:09:05 -07001007 mContactCard.setOnCreateContextMenuListener(mEntryContextMenuListener);
Paul Soulosb3054e52014-06-05 16:46:02 -07001008
1009 mRecentCard.setOnClickListener(mEntryClickHandler);
1010 mRecentCard.setTitle(getResources().getString(R.string.recent_card_title));
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001011
Paul Souloseb64a4b2014-07-07 17:03:27 -07001012 mAboutCard.setOnClickListener(mEntryClickHandler);
Paul Soulos2a4207f2014-07-31 17:09:05 -07001013 mAboutCard.setOnCreateContextMenuListener(mEntryContextMenuListener);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001014
Brian Attwell02eaf112014-08-27 21:03:42 -07001015 mPhotoView = (QuickContactImageView) findViewById(R.id.photo);
Brian Attwell63176c92014-08-18 15:14:18 -07001016 final View transparentView = findViewById(R.id.transparent_view);
Brian Attwell0d90afe2014-06-18 16:45:41 -07001017 if (mScroller != null) {
Brian Attwell63176c92014-08-18 15:14:18 -07001018 transparentView.setOnClickListener(new OnClickListener() {
Brian Attwell0d90afe2014-06-18 16:45:41 -07001019 @Override
1020 public void onClick(View v) {
1021 mScroller.scrollOffBottom();
1022 }
1023 });
1024 }
Yorke Lee62196682013-09-06 18:34:17 -07001025
Brian Attwelld41ab8a2014-08-07 11:08:55 -07001026 // Allow a shadow to be shown under the toolbar.
1027 ViewUtil.addRectangularOutlineProvider(findViewById(R.id.toolbar_parent), getResources());
1028
Brian Attwelld28851f2014-06-10 13:25:07 -07001029 final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
1030 setActionBar(toolbar);
Brian Attwell9b889e62014-06-23 18:25:32 -07001031 getActionBar().setTitle(null);
1032 // Put a TextView with a known resource id into the ActionBar. This allows us to easily
1033 // find the correct TextView location & size later.
1034 toolbar.addView(getLayoutInflater().inflate(R.layout.quickcontact_title_placeholder, null));
Yorke Lee62196682013-09-06 18:34:17 -07001035
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001036 mHasAlreadyBeenOpened = savedInstanceState != null;
Brian Attwellf1076dc2014-08-12 21:28:21 -07001037 mIsEntranceAnimationFinished = mHasAlreadyBeenOpened;
Brian Attwell9b889e62014-06-23 18:25:32 -07001038 mWindowScrim = new ColorDrawable(SCRIM_COLOR);
Brian Attwellf1076dc2014-08-12 21:28:21 -07001039 mWindowScrim.setAlpha(0);
Brian Attwell9b889e62014-06-23 18:25:32 -07001040 getWindow().setBackgroundDrawable(mWindowScrim);
Makoto Onukidfe8cc82012-05-17 18:03:44 -07001041
Brian Attwellc33ae172014-07-01 14:21:37 -07001042 mScroller.initialize(mMultiShrinkScrollerListener, mExtraMode == MODE_FULLY_EXPANDED);
Brian Attwellac3ed8e2014-06-27 17:24:42 -07001043 // mScroller needs to perform asynchronous measurements after initalize(), therefore
1044 // we can't mark this as GONE.
1045 mScroller.setVisibility(View.INVISIBLE);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001046
Brian Attwell9b889e62014-06-23 18:25:32 -07001047 setHeaderNameText(R.string.missing_name);
1048
Brian Attwelld28851f2014-06-10 13:25:07 -07001049 mSelectAccountFragmentListener= (SelectAccountDialogFragmentListener) getFragmentManager()
1050 .findFragmentByTag(FRAGMENT_TAG_SELECT_ACCOUNT);
1051 if (mSelectAccountFragmentListener == null) {
1052 mSelectAccountFragmentListener = new SelectAccountDialogFragmentListener();
1053 getFragmentManager().beginTransaction().add(0, mSelectAccountFragmentListener,
1054 FRAGMENT_TAG_SELECT_ACCOUNT).commit();
1055 mSelectAccountFragmentListener.setRetainInstance(true);
1056 }
1057 mSelectAccountFragmentListener.setQuickContactActivity(this);
Paul Soulos23889ba2014-06-10 14:55:32 -07001058
Brian Attwellf1076dc2014-08-12 21:28:21 -07001059 SchedulingUtils.doOnPreDraw(mScroller, /* drawNextFrame = */ true,
1060 new Runnable() {
1061 @Override
1062 public void run() {
1063 if (!mHasAlreadyBeenOpened) {
1064 // The initial scrim opacity must match the scrim opacity that would be
1065 // achieved by scrolling to the starting position.
1066 final float alphaRatio = mExtraMode == MODE_FULLY_EXPANDED ?
1067 1 : mScroller.getStartingTransparentHeightRatio();
1068 final int duration = getResources().getInteger(
1069 android.R.integer.config_shortAnimTime);
1070 final int desiredAlpha = (int) (0xFF * alphaRatio);
1071 ObjectAnimator o = ObjectAnimator.ofInt(mWindowScrim, "alpha", 0,
1072 desiredAlpha).setDuration(duration);
1073
1074 o.start();
1075 }
1076 }
1077 });
1078
Brian Attwell9b889e62014-06-23 18:25:32 -07001079 if (savedInstanceState != null) {
1080 final int color = savedInstanceState.getInt(KEY_THEME_COLOR, 0);
Brian Attwellac3ed8e2014-06-27 17:24:42 -07001081 SchedulingUtils.doOnPreDraw(mScroller, /* drawNextFrame = */ false,
1082 new Runnable() {
1083 @Override
1084 public void run() {
1085 // Need to wait for the pre draw before setting the initial scroll
1086 // value. Prior to pre draw all scroll values are invalid.
1087 if (mHasAlreadyBeenOpened) {
1088 mScroller.setVisibility(View.VISIBLE);
1089 mScroller.setScroll(mScroller.getScrollNeededToBeFullScreen());
1090 }
1091 // Need to wait for pre draw for setting the theme color. Setting the
1092 // header tint before the MultiShrinkScroller has been measured will
1093 // cause incorrect tinting calculations.
1094 if (color != 0) {
Brian Attwella3859ed2014-07-15 13:51:55 -07001095 setThemeColor(mMaterialColorMapUtils
1096 .calculatePrimaryAndSecondaryColor(color));
Brian Attwell9b889e62014-06-23 18:25:32 -07001097 }
Brian Attwellac3ed8e2014-06-27 17:24:42 -07001098 }
1099 });
Brian Attwell9b889e62014-06-23 18:25:32 -07001100 }
1101
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001102 Trace.endSection();
1103 }
1104
Paul Soulos405ae402014-07-14 16:16:36 -07001105 @Override
1106 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Wenyi Wangd1cb9042015-11-05 17:17:59 -08001107 final boolean deletedOrSplit = requestCode == REQUEST_CODE_CONTACT_EDITOR_ACTIVITY &&
1108 (resultCode == ContactDeletionInteraction.RESULT_CODE_DELETED ||
1109 resultCode == ContactEditorBaseActivity.RESULT_CODE_SPLIT);
1110 if (deletedOrSplit) {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001111 finish();
Paul Soulosab54ea12014-08-28 17:22:38 -07001112 } else if (requestCode == REQUEST_CODE_CONTACT_SELECTION_ACTIVITY &&
1113 resultCode != RESULT_CANCELED) {
1114 processIntent(data);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001115 }
1116 }
1117
1118 @Override
1119 protected void onNewIntent(Intent intent) {
1120 super.onNewIntent(intent);
1121 mHasAlreadyBeenOpened = true;
Brian Attwellf1076dc2014-08-12 21:28:21 -07001122 mIsEntranceAnimationFinished = true;
Brian Attwell8571dd32014-06-23 23:29:10 -07001123 mHasComputedThemeColor = false;
Brian Attwelld28851f2014-06-10 13:25:07 -07001124 processIntent(intent);
1125 }
1126
Brian Attwell9b889e62014-06-23 18:25:32 -07001127 @Override
1128 public void onSaveInstanceState(Bundle savedInstanceState) {
1129 super.onSaveInstanceState(savedInstanceState);
1130 if (mColorFilter != null) {
Brian Attwell8e29faf2015-01-21 10:22:40 -08001131 savedInstanceState.putInt(KEY_THEME_COLOR, mColorFilterColor);
Brian Attwell9b889e62014-06-23 18:25:32 -07001132 }
Tingting Wange3a7c4f2015-09-24 18:23:18 -07001133 savedInstanceState.putBoolean(KEY_IS_SUGGESTION_LIST_COLLAPSED, mIsSuggestionListCollapsed);
Tingting Wang18906c02015-10-16 14:53:10 -07001134 savedInstanceState.putLong(KEY_PREVIOUS_CONTACT_ID, mPreviousContactId);
1135 savedInstanceState.putBoolean(
1136 KEY_SUGGESTIONS_AUTO_SELECTED, mSuggestionsShouldAutoSelected);
Tingting Wange3a7c4f2015-09-24 18:23:18 -07001137 savedInstanceState.putSerializable(
1138 KEY_SELECTED_SUGGESTION_CONTACTS, mSelectedAggregationIds);
Brian Attwell9b889e62014-06-23 18:25:32 -07001139 }
1140
Brian Attwelld28851f2014-06-10 13:25:07 -07001141 private void processIntent(Intent intent) {
Paul Soulosf19dda92014-09-15 15:48:02 -07001142 if (intent == null) {
1143 finish();
1144 return;
1145 }
Brian Attwelld28851f2014-06-10 13:25:07 -07001146 Uri lookupUri = intent.getData();
1147
1148 // Check to see whether it comes from the old version.
1149 if (lookupUri != null && LEGACY_AUTHORITY.equals(lookupUri.getAuthority())) {
1150 final long rawContactId = ContentUris.parseId(lookupUri);
1151 lookupUri = RawContacts.getContactLookupUri(getContentResolver(),
1152 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
1153 }
Brian Attwell05287bf2015-02-25 22:24:04 -08001154 mExtraMode = getIntent().getIntExtra(QuickContact.EXTRA_MODE, QuickContact.MODE_LARGE);
1155 mExtraPrioritizedMimeType = getIntent().getStringExtra(QuickContact.EXTRA_PRIORITIZED_MIMETYPE);
Brian Attwelld28851f2014-06-10 13:25:07 -07001156 final Uri oldLookupUri = mLookupUri;
1157
Jay Shraunerfcfcae32014-11-24 10:23:36 -08001158 if (lookupUri == null) {
1159 finish();
1160 return;
1161 }
1162 mLookupUri = lookupUri;
Brian Attwelld28851f2014-06-10 13:25:07 -07001163 mExcludeMimes = intent.getStringArrayExtra(QuickContact.EXTRA_EXCLUDE_MIMES);
1164 if (oldLookupUri == null) {
1165 mContactLoader = (ContactLoader) getLoaderManager().initLoader(
1166 LOADER_CONTACT_ID, null, mLoaderContactCallbacks);
1167 } else if (oldLookupUri != mLookupUri) {
1168 // After copying a directory contact, the contact URI changes. Therefore,
Wenyi Wang144a4d12016-02-17 11:46:23 -08001169 // we need to reload the new contact.
Brian Attwell59a585b2014-10-17 18:07:10 -07001170 destroyInteractionLoaders();
Wenyi Wang144a4d12016-02-17 11:46:23 -08001171 mContactLoader = (ContactLoader) (Loader<?>) getLoaderManager().getLoader(
1172 LOADER_CONTACT_ID);
Walter Jang27a2fac2016-03-14 10:44:14 -07001173 mContactLoader.setLookupUri(mLookupUri);
Brian Attwell59a585b2014-10-17 18:07:10 -07001174 mCachedCp2DataCardModel = null;
Brian Attwelld28851f2014-06-10 13:25:07 -07001175 }
Wenyi Wang144a4d12016-02-17 11:46:23 -08001176 mContactLoader.forceLoad();
Brian Attwell2d150da2014-07-09 22:35:56 -07001177
1178 NfcHandler.register(this, mLookupUri);
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001179 }
1180
Brian Attwell59a585b2014-10-17 18:07:10 -07001181 private void destroyInteractionLoaders() {
1182 for (int interactionLoaderId : mRecentLoaderIds) {
1183 getLoaderManager().destroyLoader(interactionLoaderId);
1184 }
1185 }
1186
Brian Attwellb7e43642014-06-02 14:33:04 -07001187 private void runEntranceAnimation() {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001188 if (mHasAlreadyBeenOpened) {
1189 return;
1190 }
1191 mHasAlreadyBeenOpened = true;
Brian Attwellc33ae172014-07-01 14:21:37 -07001192 mScroller.scrollUpForEntranceAnimation(mExtraMode != MODE_FULLY_EXPANDED);
Brian Attwellb7e43642014-06-02 14:33:04 -07001193 }
1194
Katherine Kuan81281ee2011-07-28 16:20:59 -07001195 /** Assign this string to the view if it is not empty. */
Brian Attwelld28851f2014-06-10 13:25:07 -07001196 private void setHeaderNameText(int resId) {
Brian Attwell9b889e62014-06-23 18:25:32 -07001197 if (mScroller != null) {
Tingting Wang9f153a02015-11-05 18:11:04 -08001198 mScroller.setTitle(getText(resId) == null ? null : getText(resId).toString(),
1199 /* isPhoneNumber= */ false);
Brian Attwell9b889e62014-06-23 18:25:32 -07001200 }
Katherine Kuan81281ee2011-07-28 16:20:59 -07001201 }
1202
1203 /** Assign this string to the view if it is not empty. */
Tingting Wang9f153a02015-11-05 18:11:04 -08001204 private void setHeaderNameText(String value, boolean isPhoneNumber) {
Brian Attwelld28851f2014-06-10 13:25:07 -07001205 if (!TextUtils.isEmpty(value)) {
Brian Attwell9b889e62014-06-23 18:25:32 -07001206 if (mScroller != null) {
Tingting Wang9f153a02015-11-05 18:11:04 -08001207 mScroller.setTitle(value, isPhoneNumber);
Brian Attwell9b889e62014-06-23 18:25:32 -07001208 }
Katherine Kuan81281ee2011-07-28 16:20:59 -07001209 }
1210 }
1211
1212 /**
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001213 * Check if the given MIME-type appears in the list of excluded MIME-types
1214 * that the most-recent caller requested.
1215 */
1216 private boolean isMimeExcluded(String mimeType) {
1217 if (mExcludeMimes == null) return false;
1218 for (String excludedMime : mExcludeMimes) {
1219 if (TextUtils.equals(excludedMime, mimeType)) {
1220 return true;
1221 }
1222 }
1223 return false;
1224 }
1225
1226 /**
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02001227 * Handle the result from the ContactLoader
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001228 */
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001229 private void bindContactData(final Contact data) {
1230 Trace.beginSection("bindContactData");
Yorke Lee6cb6f6f2013-09-26 16:26:03 -07001231 mContactData = data;
Brian Attwelld28851f2014-06-10 13:25:07 -07001232 invalidateOptionsMenu();
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001233
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001234 Trace.endSection();
1235 Trace.beginSection("Set display photo & name");
Makoto Onukidfe8cc82012-05-17 18:03:44 -07001236
Brian Attwell02eaf112014-08-27 21:03:42 -07001237 mPhotoView.setIsBusiness(mContactData.isDisplayNameFromOrganization());
Yorke Lee62196682013-09-06 18:34:17 -07001238 mPhotoSetter.setupContactPhoto(data, mPhotoView);
Brian Attwell31b2d422014-06-05 00:14:58 -07001239 extractAndApplyTintFromPhotoViewAsynchronously();
Tingting Wangdf65d162015-07-24 17:04:35 -07001240 final String displayName = ContactDisplayUtils.getDisplayName(this, data).toString();
Tingting Wang9f153a02015-11-05 18:11:04 -08001241 setHeaderNameText(
1242 displayName, mContactData.getDisplayNameSource() == DisplayNameSources.PHONE);
Tingting Wangdf65d162015-07-24 17:04:35 -07001243 final String phoneticName = ContactDisplayUtils.getPhoneticName(this, data);
1244 if (mScroller != null) {
Tingting Wang469a70f2016-03-08 14:50:29 -08001245 // Show phonetic name only when it doesn't equal the display name.
1246 if (!TextUtils.isEmpty(phoneticName) && !phoneticName.equals(displayName)) {
Tingting Wangdf65d162015-07-24 17:04:35 -07001247 mScroller.setPhoneticName(phoneticName);
1248 } else {
1249 mScroller.setPhoneticNameGone();
1250 }
Tingting Wangd5b686e2015-07-13 12:52:40 -07001251 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001252
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001253 Trace.endSection();
Makoto Onukidfe8cc82012-05-17 18:03:44 -07001254
Brian Attwell6bb01342014-08-20 23:16:00 -07001255 mEntriesAndActionsTask = new AsyncTask<Void, Void, Cp2DataCardModel>() {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001256
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001257 @Override
Brian Attwell6bb01342014-08-20 23:16:00 -07001258 protected Cp2DataCardModel doInBackground(
Paul Souloseb64a4b2014-07-07 17:03:27 -07001259 Void... params) {
1260 return generateDataModelFromContact(data);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001261 }
1262
1263 @Override
Brian Attwell6bb01342014-08-20 23:16:00 -07001264 protected void onPostExecute(Cp2DataCardModel cardDataModel) {
1265 super.onPostExecute(cardDataModel);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001266 // Check that original AsyncTask parameters are still valid and the activity
1267 // is still running before binding to UI. A new intent could invalidate
1268 // the results, for example.
1269 if (data == mContactData && !isCancelled()) {
Brian Attwell6bb01342014-08-20 23:16:00 -07001270 bindDataToCards(cardDataModel);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001271 showActivity();
1272 }
1273 }
1274 };
1275 mEntriesAndActionsTask.execute();
1276 }
1277
Brian Attwell6bb01342014-08-20 23:16:00 -07001278 private void bindDataToCards(Cp2DataCardModel cp2DataCardModel) {
1279 startInteractionLoaders(cp2DataCardModel);
Wenyi Wanga26a57a2016-03-02 17:53:23 -08001280 populateContactAndAboutCard(cp2DataCardModel, /* shouldAddPhoneticName */ true);
Tingting Wange3a7c4f2015-09-24 18:23:18 -07001281 populateSuggestionCard();
Paul Souloseb64a4b2014-07-07 17:03:27 -07001282 }
1283
Brian Attwell6bb01342014-08-20 23:16:00 -07001284 private void startInteractionLoaders(Cp2DataCardModel cp2DataCardModel) {
1285 final Map<String, List<DataItem>> dataItemsMap = cp2DataCardModel.dataItemsMap;
1286 final List<DataItem> phoneDataItems = dataItemsMap.get(Phone.CONTENT_ITEM_TYPE);
Paul Soulos97f27802014-09-08 13:55:45 -07001287 if (phoneDataItems != null && phoneDataItems.size() == 1) {
1288 mOnlyOnePhoneNumber = true;
1289 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001290 String[] phoneNumbers = null;
1291 if (phoneDataItems != null) {
1292 phoneNumbers = new String[phoneDataItems.size()];
1293 for (int i = 0; i < phoneDataItems.size(); ++i) {
1294 phoneNumbers[i] = ((PhoneDataItem) phoneDataItems.get(i)).getNumber();
1295 }
1296 }
Paul Soulosab840442014-06-17 14:08:40 -07001297 final Bundle phonesExtraBundle = new Bundle();
Paul Souloseb64a4b2014-07-07 17:03:27 -07001298 phonesExtraBundle.putStringArray(KEY_LOADER_EXTRA_PHONES, phoneNumbers);
1299
1300 Trace.beginSection("start sms loader");
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001301 getLoaderManager().initLoader(
1302 LOADER_SMS_ID,
Paul Soulosab840442014-06-17 14:08:40 -07001303 phonesExtraBundle,
1304 mLoaderInteractionsCallbacks);
1305 Trace.endSection();
1306
1307 Trace.beginSection("start call log loader");
1308 getLoaderManager().initLoader(
1309 LOADER_CALL_LOG_ID,
1310 phonesExtraBundle,
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001311 mLoaderInteractionsCallbacks);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001312 Trace.endSection();
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001313
Paul Souloseb64a4b2014-07-07 17:03:27 -07001314
Paul Soulos899aa212014-06-11 12:04:43 -07001315 Trace.beginSection("start calendar loader");
Brian Attwell6bb01342014-08-20 23:16:00 -07001316 final List<DataItem> emailDataItems = dataItemsMap.get(Email.CONTENT_ITEM_TYPE);
Paul Soulos97f27802014-09-08 13:55:45 -07001317 if (emailDataItems != null && emailDataItems.size() == 1) {
1318 mOnlyOneEmail = true;
1319 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001320 String[] emailAddresses = null;
1321 if (emailDataItems != null) {
1322 emailAddresses = new String[emailDataItems.size()];
1323 for (int i = 0; i < emailDataItems.size(); ++i) {
1324 emailAddresses[i] = ((EmailDataItem) emailDataItems.get(i)).getAddress();
1325 }
1326 }
Paul Soulosab840442014-06-17 14:08:40 -07001327 final Bundle emailsExtraBundle = new Bundle();
Paul Souloseb64a4b2014-07-07 17:03:27 -07001328 emailsExtraBundle.putStringArray(KEY_LOADER_EXTRA_EMAILS, emailAddresses);
Paul Soulos899aa212014-06-11 12:04:43 -07001329 getLoaderManager().initLoader(
1330 LOADER_CALENDAR_ID,
Paul Soulosab840442014-06-17 14:08:40 -07001331 emailsExtraBundle,
Paul Soulos899aa212014-06-11 12:04:43 -07001332 mLoaderInteractionsCallbacks);
1333 Trace.endSection();
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001334 }
1335
1336 private void showActivity() {
1337 if (mScroller != null) {
1338 mScroller.setVisibility(View.VISIBLE);
1339 SchedulingUtils.doOnPreDraw(mScroller, /* drawNextFrame = */ false,
1340 new Runnable() {
1341 @Override
1342 public void run() {
1343 runEntranceAnimation();
1344 }
1345 });
1346 }
1347 }
1348
Brian Attwell6bb01342014-08-20 23:16:00 -07001349 private List<List<Entry>> buildAboutCardEntries(Map<String, List<DataItem>> dataItemsMap) {
Paul Soulosa153dba2014-08-06 13:44:57 -07001350 final List<List<Entry>> aboutCardEntries = new ArrayList<>();
1351 for (String mimetype : SORTED_ABOUT_CARD_MIMETYPES) {
Brian Attwell6bb01342014-08-20 23:16:00 -07001352 final List<DataItem> mimeTypeItems = dataItemsMap.get(mimetype);
Paul Soulosa153dba2014-08-06 13:44:57 -07001353 if (mimeTypeItems == null) {
1354 continue;
1355 }
Brian Attwell6bb01342014-08-20 23:16:00 -07001356 // Set aboutCardTitleOut = null, since SORTED_ABOUT_CARD_MIMETYPES doesn't contain
1357 // the name mimetype.
1358 final List<Entry> aboutEntries = dataItemsToEntries(mimeTypeItems,
1359 /* aboutCardTitleOut = */ null);
Paul Soulosa153dba2014-08-06 13:44:57 -07001360 if (aboutEntries.size() > 0) {
1361 aboutCardEntries.add(aboutEntries);
1362 }
1363 }
1364 return aboutCardEntries;
1365 }
1366
Paul Soulosa42ef762014-08-20 10:26:10 -07001367 @Override
1368 protected void onResume() {
1369 super.onResume();
1370 // If returning from a launched activity, repopulate the contact and about card
1371 if (mHasIntentLaunched) {
1372 mHasIntentLaunched = false;
Wenyi Wanga26a57a2016-03-02 17:53:23 -08001373 populateContactAndAboutCard(mCachedCp2DataCardModel, /* shouldAddPhoneticName */ false);
Paul Soulosa42ef762014-08-20 10:26:10 -07001374 }
Brian Attwell59a585b2014-10-17 18:07:10 -07001375
1376 // When exiting the activity and resuming, we want to force a full reload of all the
1377 // interaction data in case something changed in the background. On screen rotation,
1378 // we don't need to do this. And, mCachedCp2DataCardModel will be null, so we won't.
1379 if (mCachedCp2DataCardModel != null) {
1380 destroyInteractionLoaders();
1381 startInteractionLoaders(mCachedCp2DataCardModel);
1382 }
Paul Soulosa42ef762014-08-20 10:26:10 -07001383 }
1384
Tingting Wange3a7c4f2015-09-24 18:23:18 -07001385 private void populateSuggestionCard() {
1386 // Initialize suggestion related view and data.
Tingting Wang18906c02015-10-16 14:53:10 -07001387 if (mPreviousContactId != mContactData.getId()) {
1388 mCollapsedSuggestionCardView.setVisibility(View.GONE);
1389 mExpandSuggestionCardView.setVisibility(View.GONE);
1390 mIsSuggestionListCollapsed = true;
Tingting Wang42a08d92015-11-20 11:26:21 -08001391 mSuggestionsShouldAutoSelected = true;
Tingting Wang0fafb0e2015-10-14 12:18:45 -07001392 mSuggestionList.removeAllViews();
1393 }
Tingting Wange3a7c4f2015-09-24 18:23:18 -07001394
Tingting Wang3d65ea52015-11-13 09:36:02 -08001395 // Do not show the card when it's directory contact or invisible.
1396 if (DirectoryContactUtil.isDirectoryContact(mContactData)
1397 || InvisibleContactUtil.isInvisibleAndAddable(mContactData, this)) {
1398 return;
1399 }
1400
Tingting Wange3a7c4f2015-09-24 18:23:18 -07001401 if (mAggregationSuggestionEngine == null) {
1402 mAggregationSuggestionEngine = new AggregationSuggestionEngine(this);
1403 mAggregationSuggestionEngine.setListener(this);
Tingting Wange29b6942015-09-30 15:21:33 -07001404 mAggregationSuggestionEngine.setSuggestionsLimit(getResources().getInteger(
1405 R.integer.quickcontact_suggestions_limit));
Tingting Wange3a7c4f2015-09-24 18:23:18 -07001406 mAggregationSuggestionEngine.start();
1407 }
1408
1409 mAggregationSuggestionEngine.setContactId(mContactData.getId());
Tingting Wang18906c02015-10-16 14:53:10 -07001410 if (mPreviousContactId != 0
1411 && mPreviousContactId != mContactData.getId()) {
Tingting Wange29b6942015-09-30 15:21:33 -07001412 // Clear selected Ids when listing suggestions for new contact Id.
1413 mSelectedAggregationIds.clear();
1414 }
Tingting Wang18906c02015-10-16 14:53:10 -07001415 mPreviousContactId = mContactData.getId();
Tingting Wange3a7c4f2015-09-24 18:23:18 -07001416
1417 // Trigger suggestion engine to compute suggestions.
Tingting Wanga1af0c82015-10-23 10:53:58 -07001418 if (mContactData.getId() <= 0) {
1419 return;
1420 }
Tingting Wange3a7c4f2015-09-24 18:23:18 -07001421 final ContentValues values = new ContentValues();
1422 values.put(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
1423 mContactData.getDisplayName());
1424 values.put(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_FAMILY_NAME,
1425 mContactData.getPhoneticName());
1426 mAggregationSuggestionEngine.onNameChange(ValuesDelta.fromBefore(values));
1427 }
1428
Wenyi Wanga26a57a2016-03-02 17:53:23 -08001429 private void populateContactAndAboutCard(Cp2DataCardModel cp2DataCardModel,
1430 boolean shouldAddPhoneticName) {
Brian Attwell6bb01342014-08-20 23:16:00 -07001431 mCachedCp2DataCardModel = cp2DataCardModel;
1432 if (mHasIntentLaunched || cp2DataCardModel == null) {
Paul Soulosa42ef762014-08-20 10:26:10 -07001433 return;
1434 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001435 Trace.beginSection("bind contact card");
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001436
Brian Attwell6bb01342014-08-20 23:16:00 -07001437 final List<List<Entry>> contactCardEntries = cp2DataCardModel.contactCardEntries;
1438 final List<List<Entry>> aboutCardEntries = cp2DataCardModel.aboutCardEntries;
1439 final String customAboutCardName = cp2DataCardModel.customAboutCardName;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001440
1441 if (contactCardEntries.size() > 0) {
Brian Attwell05287bf2015-02-25 22:24:04 -08001442 final boolean firstEntriesArePrioritizedMimeType =
1443 !TextUtils.isEmpty(mExtraPrioritizedMimeType) &&
1444 mCachedCp2DataCardModel.dataItemsMap.containsKey(mExtraPrioritizedMimeType) &&
1445 mCachedCp2DataCardModel.dataItemsMap.get(mExtraPrioritizedMimeType).size() != 0;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001446 mContactCard.initialize(contactCardEntries,
1447 /* numInitialVisibleEntries = */ MIN_NUM_CONTACT_ENTRIES_SHOWN,
Paul Soulosc8e2a912014-07-21 14:52:20 -07001448 /* isExpanded = */ mContactCard.isExpanded(),
Paul Soulos89966b42014-07-21 12:38:50 -07001449 /* isAlwaysExpanded = */ false,
Paul Soulos0cda9ae2014-07-23 11:27:28 -07001450 mExpandingEntryCardViewListener,
Brian Attwell05287bf2015-02-25 22:24:04 -08001451 mScroller,
1452 firstEntriesArePrioritizedMimeType);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001453 mContactCard.setVisibility(View.VISIBLE);
1454 } else {
1455 mContactCard.setVisibility(View.GONE);
1456 }
1457 Trace.endSection();
1458
1459 Trace.beginSection("bind about card");
Paul Soulosc6e11092014-07-22 09:22:27 -07001460 // Phonetic name is not a data item, so the entry needs to be created separately
Wenyi Wanga26a57a2016-03-02 17:53:23 -08001461 // But if mCachedCp2DataCardModel is passed to this method (e.g. returning from editor
1462 // without saving any changes), then it should include phoneticName and the phoneticName
1463 // shouldn't be changed. If this is the case, we shouldn't add it again. b/27459294
Paul Soulosc6e11092014-07-22 09:22:27 -07001464 final String phoneticName = mContactData.getPhoneticName();
Wenyi Wanga26a57a2016-03-02 17:53:23 -08001465 if (shouldAddPhoneticName && !TextUtils.isEmpty(phoneticName)) {
Paul Soulosc6e11092014-07-22 09:22:27 -07001466 Entry phoneticEntry = new Entry(/* viewId = */ -1,
1467 /* icon = */ null,
1468 getResources().getString(R.string.name_phonetic),
1469 phoneticName,
Walter Jang8a3fe522014-10-16 11:12:13 -07001470 /* subHeaderIcon = */ null,
Paul Soulosc6e11092014-07-22 09:22:27 -07001471 /* text = */ null,
Walter Jang8a3fe522014-10-16 11:12:13 -07001472 /* textIcon = */ null,
Paul Soulos23e28362014-08-29 14:57:08 -07001473 /* primaryContentDescription = */ null,
Paul Soulosc6e11092014-07-22 09:22:27 -07001474 /* intent = */ null,
1475 /* alternateIcon = */ null,
1476 /* alternateIntent = */ null,
1477 /* alternateContentDescription = */ null,
1478 /* shouldApplyColor = */ false,
Paul Soulos2a4207f2014-07-31 17:09:05 -07001479 /* isEditable = */ false,
1480 /* EntryContextMenuInfo = */ new EntryContextMenuInfo(phoneticName,
Paul Soulos97f27802014-09-08 13:55:45 -07001481 getResources().getString(R.string.name_phonetic),
1482 /* mimeType = */ null, /* id = */ -1, /* isPrimary = */ false),
Paul Soulos48fc9122014-08-26 13:52:36 -07001483 /* thirdIcon = */ null,
1484 /* thirdIntent = */ null,
Paul Soulos48290be2014-09-08 13:44:51 -07001485 /* thirdContentDescription = */ null,
Tyler Gunn5f87e922015-08-05 14:24:52 -07001486 /* thirdAction = */ Entry.ACTION_NONE,
1487 /* thirdExtras = */ null,
1488 /* iconResourceId = */ 0);
Paul Soulosc6e11092014-07-22 09:22:27 -07001489 List<Entry> phoneticList = new ArrayList<>();
1490 phoneticList.add(phoneticEntry);
Paul Soulosa153dba2014-08-06 13:44:57 -07001491 // Phonetic name comes after nickname. Check to see if the first entry type is nickname
Paul Soulosa0fa4c12014-08-07 12:01:32 -07001492 if (aboutCardEntries.size() > 0 && aboutCardEntries.get(0).get(0).getHeader().equals(
Paul Soulosa153dba2014-08-06 13:44:57 -07001493 getResources().getString(R.string.header_nickname_entry))) {
1494 aboutCardEntries.add(1, phoneticList);
1495 } else {
1496 aboutCardEntries.add(0, phoneticList);
1497 }
Paul Soulosc6e11092014-07-22 09:22:27 -07001498 }
1499
Brian Attwell6bb01342014-08-20 23:16:00 -07001500 if (!TextUtils.isEmpty(customAboutCardName)) {
1501 mAboutCard.setTitle(customAboutCardName);
1502 }
1503
kungaox3b1a8b22015-02-05 15:29:19 +08001504 mAboutCard.initialize(aboutCardEntries,
1505 /* numInitialVisibleEntries = */ 1,
1506 /* isExpanded = */ true,
1507 /* isAlwaysExpanded = */ true,
1508 mExpandingEntryCardViewListener,
1509 mScroller);
Brian Attwell60953692014-07-11 17:18:46 -07001510
1511 if (contactCardEntries.size() == 0 && aboutCardEntries.size() == 0) {
1512 initializeNoContactDetailCard();
1513 } else {
1514 mNoContactDetailsCard.setVisibility(View.GONE);
1515 }
1516
Paul Soulosa8fce962014-07-15 13:37:24 -07001517 // If the Recent card is already initialized (all recent data is loaded), show the About
1518 // card if it has entries. Otherwise About card visibility will be set in bindRecentData()
1519 if (isAllRecentDataLoaded() && aboutCardEntries.size() > 0) {
1520 mAboutCard.setVisibility(View.VISIBLE);
1521 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001522 Trace.endSection();
1523 }
1524
1525 /**
Brian Attwell60953692014-07-11 17:18:46 -07001526 * Create a card that shows "Add email" and "Add phone number" entries in grey.
1527 */
1528 private void initializeNoContactDetailCard() {
1529 final Drawable phoneIcon = getResources().getDrawable(
1530 R.drawable.ic_phone_24dp).mutate();
1531 final Entry phonePromptEntry = new Entry(CARD_ENTRY_ID_EDIT_CONTACT,
1532 phoneIcon, getString(R.string.quickcontact_add_phone_number),
Walter Jang8a3fe522014-10-16 11:12:13 -07001533 /* subHeader = */ null, /* subHeaderIcon = */ null, /* text = */ null,
1534 /* textIcon = */ null, /* primaryContentDescription = */ null,
Paul Soulos23e28362014-08-29 14:57:08 -07001535 getEditContactIntent(),
Paul Soulosdd7419d2014-07-15 11:22:13 -07001536 /* alternateIcon = */ null, /* alternateIntent = */ null,
Paul Soulos714455b2014-07-15 14:32:01 -07001537 /* alternateContentDescription = */ null, /* shouldApplyColor = */ true,
Paul Soulos48fc9122014-08-26 13:52:36 -07001538 /* isEditable = */ false, /* EntryContextMenuInfo = */ null,
1539 /* thirdIcon = */ null, /* thirdIntent = */ null,
Tyler Gunn5f87e922015-08-05 14:24:52 -07001540 /* thirdContentDescription = */ null,
1541 /* thirdAction = */ Entry.ACTION_NONE,
1542 /* thirdExtras = */ null,
1543 R.drawable.ic_phone_24dp);
Brian Attwell60953692014-07-11 17:18:46 -07001544
1545 final Drawable emailIcon = getResources().getDrawable(
1546 R.drawable.ic_email_24dp).mutate();
1547 final Entry emailPromptEntry = new Entry(CARD_ENTRY_ID_EDIT_CONTACT,
1548 emailIcon, getString(R.string.quickcontact_add_email), /* subHeader = */ null,
Walter Jang8a3fe522014-10-16 11:12:13 -07001549 /* subHeaderIcon = */ null,
1550 /* text = */ null, /* textIcon = */ null, /* primaryContentDescription = */ null,
Paul Soulos23e28362014-08-29 14:57:08 -07001551 getEditContactIntent(), /* alternateIcon = */ null,
Paul Soulosdd7419d2014-07-15 11:22:13 -07001552 /* alternateIntent = */ null, /* alternateContentDescription = */ null,
Paul Soulos2a4207f2014-07-31 17:09:05 -07001553 /* shouldApplyColor = */ true, /* isEditable = */ false,
Paul Soulos48fc9122014-08-26 13:52:36 -07001554 /* EntryContextMenuInfo = */ null, /* thirdIcon = */ null,
Paul Soulos48290be2014-09-08 13:44:51 -07001555 /* thirdIntent = */ null, /* thirdContentDescription = */ null,
Tyler Gunn5f87e922015-08-05 14:24:52 -07001556 /* thirdAction = */ Entry.ACTION_NONE, /* thirdExtras = */ null,
Paul Soulos48290be2014-09-08 13:44:51 -07001557 R.drawable.ic_email_24dp);
Brian Attwell60953692014-07-11 17:18:46 -07001558
1559 final List<List<Entry>> promptEntries = new ArrayList<>();
1560 promptEntries.add(new ArrayList<Entry>(1));
1561 promptEntries.add(new ArrayList<Entry>(1));
1562 promptEntries.get(0).add(phonePromptEntry);
1563 promptEntries.get(1).add(emailPromptEntry);
1564
1565 final int subHeaderTextColor = getResources().getColor(
1566 R.color.quickcontact_entry_sub_header_text_color);
1567 final PorterDuffColorFilter greyColorFilter =
1568 new PorterDuffColorFilter(subHeaderTextColor, PorterDuff.Mode.SRC_ATOP);
Paul Soulosc8e2a912014-07-21 14:52:20 -07001569 mNoContactDetailsCard.initialize(promptEntries, 2, /* isExpanded = */ true,
Paul Soulos0cda9ae2014-07-23 11:27:28 -07001570 /* isAlwaysExpanded = */ true, mExpandingEntryCardViewListener, mScroller);
Brian Attwell60953692014-07-11 17:18:46 -07001571 mNoContactDetailsCard.setVisibility(View.VISIBLE);
1572 mNoContactDetailsCard.setEntryHeaderColor(subHeaderTextColor);
1573 mNoContactDetailsCard.setColorAndFilter(subHeaderTextColor, greyColorFilter);
1574 }
1575
1576 /**
Paul Souloseb64a4b2014-07-07 17:03:27 -07001577 * Builds the {@link DataItem}s Map out of the Contact.
1578 * @param data The contact to build the data from.
1579 * @return A pair containing a list of data items sorted within mimetype and sorted
1580 * amongst mimetype. The map goes from mimetype string to the sorted list of data items within
1581 * mimetype
1582 */
Brian Attwell6bb01342014-08-20 23:16:00 -07001583 private Cp2DataCardModel generateDataModelFromContact(
Paul Souloseb64a4b2014-07-07 17:03:27 -07001584 Contact data) {
1585 Trace.beginSection("Build data items map");
1586
1587 final Map<String, List<DataItem>> dataItemsMap = new HashMap<>();
Brian Attwell8bf96e72014-06-25 15:46:47 -07001588
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001589 final ResolveCache cache = ResolveCache.getInstance(this);
Maurice Chu851222a2012-06-21 11:43:08 -07001590 for (RawContact rawContact : data.getRawContacts()) {
1591 for (DataItem dataItem : rawContact.getDataItems()) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07001592 dataItem.setRawContactId(rawContact.getId());
1593
Maurice Chu851222a2012-06-21 11:43:08 -07001594 final String mimeType = dataItem.getMimeType();
Paul Souloseb64a4b2014-07-07 17:03:27 -07001595 if (mimeType == null) continue;
1596
Chiao Cheng47b6f702012-09-07 17:28:17 -07001597 final AccountType accountType = rawContact.getAccountType(this);
1598 final DataKind dataKind = AccountTypeManager.getInstance(this)
1599 .getKindOrFallback(accountType, mimeType);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001600 if (dataKind == null) continue;
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001601
Paul Souloseb64a4b2014-07-07 17:03:27 -07001602 dataItem.setDataKind(dataKind);
1603
1604 final boolean hasData = !TextUtils.isEmpty(dataItem.buildDataString(this,
1605 dataKind));
1606
1607 if (isMimeExcluded(mimeType) || !hasData) continue;
1608
1609 List<DataItem> dataItemListByType = dataItemsMap.get(mimeType);
1610 if (dataItemListByType == null) {
1611 dataItemListByType = new ArrayList<>();
1612 dataItemsMap.put(mimeType, dataItemListByType);
Paul Soulosb3054e52014-06-05 16:46:02 -07001613 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001614 dataItemListByType.add(dataItem);
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001615 }
1616 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001617 Trace.endSection();
Makoto Onukidfe8cc82012-05-17 18:03:44 -07001618
Paul Souloseb64a4b2014-07-07 17:03:27 -07001619 Trace.beginSection("sort within mimetypes");
Paul Soulos16339332014-06-24 13:59:56 -07001620 /*
1621 * 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 -07001622 * used data items, one per mimetype. Then, within each mimetype, the list of data items
1623 * for that type is also sorted, based off of {super primary, primary, times used} in that
1624 * order.
Paul Soulos16339332014-06-24 13:59:56 -07001625 */
Paul Souloseb64a4b2014-07-07 17:03:27 -07001626 final List<List<DataItem>> dataItemsList = new ArrayList<>();
1627 for (List<DataItem> mimeTypeDataItems : dataItemsMap.values()) {
1628 // Remove duplicate data items
1629 Collapser.collapseList(mimeTypeDataItems, this);
1630 // Sort within mimetype
1631 Collections.sort(mimeTypeDataItems, mWithinMimeTypeDataItemComparator);
1632 // Add to the list of data item lists
1633 dataItemsList.add(mimeTypeDataItems);
1634 }
1635 Trace.endSection();
Paul Soulos16339332014-06-24 13:59:56 -07001636
Paul Souloseb64a4b2014-07-07 17:03:27 -07001637 Trace.beginSection("sort amongst mimetypes");
1638 // Sort amongst mimetypes to bubble up the top data items for the contact card
1639 Collections.sort(dataItemsList, mAmongstMimeTypeDataItemComparator);
1640 Trace.endSection();
1641
Brian Attwell6bb01342014-08-20 23:16:00 -07001642 Trace.beginSection("cp2 data items to entries");
1643
1644 final List<List<Entry>> contactCardEntries = new ArrayList<>();
1645 final List<List<Entry>> aboutCardEntries = buildAboutCardEntries(dataItemsMap);
1646 final MutableString aboutCardName = new MutableString();
1647
1648 for (int i = 0; i < dataItemsList.size(); ++i) {
1649 final List<DataItem> dataItemsByMimeType = dataItemsList.get(i);
1650 final DataItem topDataItem = dataItemsByMimeType.get(0);
1651 if (SORTED_ABOUT_CARD_MIMETYPES.contains(topDataItem.getMimeType())) {
1652 // About card mimetypes are built in buildAboutCardEntries, skip here
1653 continue;
1654 } else {
1655 List<Entry> contactEntries = dataItemsToEntries(dataItemsList.get(i),
1656 aboutCardName);
1657 if (contactEntries.size() > 0) {
1658 contactCardEntries.add(contactEntries);
1659 }
1660 }
1661 }
1662
1663 Trace.endSection();
1664
1665 final Cp2DataCardModel dataModel = new Cp2DataCardModel();
1666 dataModel.customAboutCardName = aboutCardName.value;
1667 dataModel.aboutCardEntries = aboutCardEntries;
1668 dataModel.contactCardEntries = contactCardEntries;
1669 dataModel.dataItemsMap = dataItemsMap;
1670 return dataModel;
1671 }
1672
1673 /**
1674 * Class used to hold the About card and Contact cards' data model that gets generated
1675 * on a background thread. All data is from CP2.
1676 */
1677 private static class Cp2DataCardModel {
1678 /**
1679 * A map between a mimetype string and the corresponding list of data items. The data items
1680 * are in sorted order using mWithinMimeTypeDataItemComparator.
1681 */
1682 public Map<String, List<DataItem>> dataItemsMap;
1683 public List<List<Entry>> aboutCardEntries;
1684 public List<List<Entry>> contactCardEntries;
1685 public String customAboutCardName;
1686 }
1687
1688 private static class MutableString {
1689 public String value;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001690 }
1691
1692 /**
1693 * Converts a {@link DataItem} into an {@link ExpandingEntryCardView.Entry} for display.
1694 * If the {@link ExpandingEntryCardView.Entry} has no visual elements, null is returned.
Brian Attwell6bb01342014-08-20 23:16:00 -07001695 *
1696 * This runs on a background thread. This is set as static to avoid accidentally adding
1697 * additional dependencies on unsafe things (like the Activity).
1698 *
Paul Souloseb64a4b2014-07-07 17:03:27 -07001699 * @param dataItem The {@link DataItem} to convert.
Paul Soulos6a2a1a72014-10-10 15:52:56 -07001700 * @param secondDataItem A second {@link DataItem} to help build a full entry for some
1701 * mimetypes
Paul Souloseb64a4b2014-07-07 17:03:27 -07001702 * @return The {@link ExpandingEntryCardView.Entry}, or null if no visual elements are present.
1703 */
Paul Soulos6a2a1a72014-10-10 15:52:56 -07001704 private static Entry dataItemToEntry(DataItem dataItem, DataItem secondDataItem,
Brian Attwell6bb01342014-08-20 23:16:00 -07001705 Context context, Contact contactData,
1706 final MutableString aboutCardName) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07001707 Drawable icon = null;
1708 String header = null;
1709 String subHeader = null;
1710 Drawable subHeaderIcon = null;
1711 String text = null;
1712 Drawable textIcon = null;
Paul Soulos23e28362014-08-29 14:57:08 -07001713 StringBuilder primaryContentDescription = new StringBuilder();
Tingting Wangb6949dc2015-11-03 13:17:12 -08001714 Spannable phoneContentDescription = null;
Wenyi Wang5da55ff2015-11-19 13:22:40 -08001715 Spannable smsContentDescription = null;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001716 Intent intent = null;
Paul Soulos48ebbaa2014-07-15 13:11:23 -07001717 boolean shouldApplyColor = true;
Paul Soulosdd7419d2014-07-15 11:22:13 -07001718 Drawable alternateIcon = null;
1719 Intent alternateIntent = null;
Paul Soulos23e28362014-08-29 14:57:08 -07001720 StringBuilder alternateContentDescription = new StringBuilder();
Paul Souloseb64a4b2014-07-07 17:03:27 -07001721 final boolean isEditable = false;
Paul Soulos2a4207f2014-07-31 17:09:05 -07001722 EntryContextMenuInfo entryContextMenuInfo = null;
Paul Soulos48fc9122014-08-26 13:52:36 -07001723 Drawable thirdIcon = null;
1724 Intent thirdIntent = null;
Tyler Gunn5f87e922015-08-05 14:24:52 -07001725 int thirdAction = Entry.ACTION_NONE;
Paul Soulos48fc9122014-08-26 13:52:36 -07001726 String thirdContentDescription = null;
Tyler Gunn5f87e922015-08-05 14:24:52 -07001727 Bundle thirdExtras = null;
Paul Soulos48290be2014-09-08 13:44:51 -07001728 int iconResourceId = 0;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001729
Brian Attwell6bb01342014-08-20 23:16:00 -07001730 context = context.getApplicationContext();
Paul Soulos23e28362014-08-29 14:57:08 -07001731 final Resources res = context.getResources();
Paul Souloseb64a4b2014-07-07 17:03:27 -07001732 DataKind kind = dataItem.getDataKind();
1733
1734 if (dataItem instanceof ImDataItem) {
1735 final ImDataItem im = (ImDataItem) dataItem;
Brian Attwell6bb01342014-08-20 23:16:00 -07001736 intent = ContactsUtils.buildImIntent(context, im).first;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001737 final boolean isEmail = im.isCreatedFromEmail();
Paul Soulos7de6f852014-07-28 14:07:28 -07001738 final int protocol;
1739 if (!im.isProtocolValid()) {
1740 protocol = Im.PROTOCOL_CUSTOM;
1741 } else {
1742 protocol = isEmail ? Im.PROTOCOL_GOOGLE_TALK : im.getProtocol();
1743 }
1744 if (protocol == Im.PROTOCOL_CUSTOM) {
1745 // If the protocol is custom, display the "IM" entry header as well to distinguish
1746 // this entry from other ones
Paul Soulos23e28362014-08-29 14:57:08 -07001747 header = res.getString(R.string.header_im_entry);
1748 subHeader = Im.getProtocolLabel(res, protocol,
Paul Soulos7de6f852014-07-28 14:07:28 -07001749 im.getCustomProtocol()).toString();
1750 text = im.getData();
1751 } else {
Paul Soulos23e28362014-08-29 14:57:08 -07001752 header = Im.getProtocolLabel(res, protocol,
Paul Soulos7de6f852014-07-28 14:07:28 -07001753 im.getCustomProtocol()).toString();
1754 subHeader = im.getData();
1755 }
Paul Soulos97f27802014-09-08 13:55:45 -07001756 entryContextMenuInfo = new EntryContextMenuInfo(im.getData(), header,
1757 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001758 } else if (dataItem instanceof OrganizationDataItem) {
1759 final OrganizationDataItem organization = (OrganizationDataItem) dataItem;
Paul Soulos23e28362014-08-29 14:57:08 -07001760 header = res.getString(R.string.header_organization_entry);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001761 subHeader = organization.getCompany();
Paul Soulos97f27802014-09-08 13:55:45 -07001762 entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header,
1763 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001764 text = organization.getTitle();
1765 } else if (dataItem instanceof NicknameDataItem) {
1766 final NicknameDataItem nickname = (NicknameDataItem) dataItem;
1767 // Build nickname entries
1768 final boolean isNameRawContact =
Brian Attwell6bb01342014-08-20 23:16:00 -07001769 (contactData.getNameRawContactId() == dataItem.getRawContactId());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001770
1771 final boolean duplicatesTitle =
1772 isNameRawContact
Brian Attwell6bb01342014-08-20 23:16:00 -07001773 && contactData.getDisplayNameSource() == DisplayNameSources.NICKNAME;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001774
1775 if (!duplicatesTitle) {
Paul Soulos23e28362014-08-29 14:57:08 -07001776 header = res.getString(R.string.header_nickname_entry);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001777 subHeader = nickname.getName();
Paul Soulos97f27802014-09-08 13:55:45 -07001778 entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header,
1779 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001780 }
1781 } else if (dataItem instanceof NoteDataItem) {
1782 final NoteDataItem note = (NoteDataItem) dataItem;
Paul Soulos23e28362014-08-29 14:57:08 -07001783 header = res.getString(R.string.header_note_entry);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001784 subHeader = note.getNote();
Paul Soulos97f27802014-09-08 13:55:45 -07001785 entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header,
1786 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001787 } else if (dataItem instanceof WebsiteDataItem) {
1788 final WebsiteDataItem website = (WebsiteDataItem) dataItem;
Paul Soulos23e28362014-08-29 14:57:08 -07001789 header = res.getString(R.string.header_website_entry);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001790 subHeader = website.getUrl();
Paul Soulos97f27802014-09-08 13:55:45 -07001791 entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header,
1792 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001793 try {
Brian Attwell3bcf09e2014-12-08 16:16:05 -08001794 final WebAddress webAddress = new WebAddress(website.buildDataStringForDisplay
1795 (context, kind));
Paul Souloseb64a4b2014-07-07 17:03:27 -07001796 intent = new Intent(Intent.ACTION_VIEW, Uri.parse(webAddress.toString()));
1797 } catch (final ParseException e) {
Brian Attwell3bcf09e2014-12-08 16:16:05 -08001798 Log.e(TAG, "Couldn't parse website: " + website.buildDataStringForDisplay(
1799 context, kind));
Paul Souloseb64a4b2014-07-07 17:03:27 -07001800 }
1801 } else if (dataItem instanceof EventDataItem) {
1802 final EventDataItem event = (EventDataItem) dataItem;
Brian Attwell3bcf09e2014-12-08 16:16:05 -08001803 final String dataString = event.buildDataStringForDisplay(context, kind);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001804 final Calendar cal = DateUtils.parseDate(dataString, false);
1805 if (cal != null) {
1806 final Date nextAnniversary =
1807 DateUtils.getNextAnnualDate(cal);
1808 final Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
1809 builder.appendPath("time");
1810 ContentUris.appendId(builder, nextAnniversary.getTime());
1811 intent = new Intent(Intent.ACTION_VIEW).setData(builder.build());
1812 }
Paul Soulos23e28362014-08-29 14:57:08 -07001813 header = res.getString(R.string.header_event_entry);
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001814 if (event.hasKindTypeColumn(kind)) {
Wenyi Wangc85af282015-12-21 15:55:32 -08001815 subHeader = EventCompat.getTypeLabel(res, event.getKindTypeColumn(kind),
Paul Soulos7de6f852014-07-28 14:07:28 -07001816 event.getLabel()).toString();
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001817 }
Brian Attwell6bb01342014-08-20 23:16:00 -07001818 text = DateUtils.formatDate(context, dataString);
Paul Soulos97f27802014-09-08 13:55:45 -07001819 entryContextMenuInfo = new EntryContextMenuInfo(text, header,
1820 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001821 } else if (dataItem instanceof RelationDataItem) {
1822 final RelationDataItem relation = (RelationDataItem) dataItem;
Brian Attwell3bcf09e2014-12-08 16:16:05 -08001823 final String dataString = relation.buildDataStringForDisplay(context, kind);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001824 if (!TextUtils.isEmpty(dataString)) {
1825 intent = new Intent(Intent.ACTION_SEARCH);
1826 intent.putExtra(SearchManager.QUERY, dataString);
1827 intent.setType(Contacts.CONTENT_TYPE);
1828 }
Paul Soulos23e28362014-08-29 14:57:08 -07001829 header = res.getString(R.string.header_relation_entry);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001830 subHeader = relation.getName();
Paul Soulos97f27802014-09-08 13:55:45 -07001831 entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header,
1832 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001833 if (relation.hasKindTypeColumn(kind)) {
Paul Soulos23e28362014-08-29 14:57:08 -07001834 text = Relation.getTypeLabel(res,
Brian Attwell6bb01342014-08-20 23:16:00 -07001835 relation.getKindTypeColumn(kind),
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001836 relation.getLabel()).toString();
1837 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001838 } else if (dataItem instanceof PhoneDataItem) {
1839 final PhoneDataItem phone = (PhoneDataItem) dataItem;
Tyler Gunn5f87e922015-08-05 14:24:52 -07001840 String phoneLabel = null;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001841 if (!TextUtils.isEmpty(phone.getNumber())) {
Paul Soulos23e28362014-08-29 14:57:08 -07001842 primaryContentDescription.append(res.getString(R.string.call_other)).append(" ");
Brian Attwell3bcf09e2014-12-08 16:16:05 -08001843 header = sBidiFormatter.unicodeWrap(phone.buildDataStringForDisplay(context, kind),
Brian Attwellc62cc792014-10-02 12:35:07 -07001844 TextDirectionHeuristics.LTR);
Paul Soulos2a4207f2014-07-31 17:09:05 -07001845 entryContextMenuInfo = new EntryContextMenuInfo(header,
Paul Soulos97f27802014-09-08 13:55:45 -07001846 res.getString(R.string.phoneLabelsGroup), dataItem.getMimeType(),
1847 dataItem.getId(), dataItem.isSuperPrimary());
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001848 if (phone.hasKindTypeColumn(kind)) {
Tingting Wang01b37682015-07-17 14:09:19 -07001849 final int kindTypeColumn = phone.getKindTypeColumn(kind);
1850 final String label = phone.getLabel();
Tyler Gunn5f87e922015-08-05 14:24:52 -07001851 phoneLabel = label;
Tingting Wang01b37682015-07-17 14:09:19 -07001852 if (kindTypeColumn == Phone.TYPE_CUSTOM && TextUtils.isEmpty(label)) {
1853 text = "";
1854 } else {
1855 text = Phone.getTypeLabel(res, kindTypeColumn, label).toString();
Tyler Gunn5f87e922015-08-05 14:24:52 -07001856 phoneLabel= text;
Tingting Wang3df785b2015-07-07 16:53:14 -07001857 primaryContentDescription.append(text).append(" ");
1858 }
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001859 }
Paul Soulos23e28362014-08-29 14:57:08 -07001860 primaryContentDescription.append(header);
Tingting Wangb6949dc2015-11-03 13:17:12 -08001861 phoneContentDescription = com.android.contacts.common.util.ContactDisplayUtils
1862 .getTelephoneTtsSpannable(primaryContentDescription.toString(), header);
Paul Soulos23e28362014-08-29 14:57:08 -07001863 icon = res.getDrawable(R.drawable.ic_phone_24dp);
Paul Soulos48290be2014-09-08 13:44:51 -07001864 iconResourceId = R.drawable.ic_phone_24dp;
Brian Attwell6bb01342014-08-20 23:16:00 -07001865 if (PhoneCapabilityTester.isPhone(context)) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07001866 intent = CallUtil.getCallIntent(phone.getNumber());
Paul Soulos16339332014-06-24 13:59:56 -07001867 }
Paul Soulosdd7419d2014-07-15 11:22:13 -07001868 alternateIntent = new Intent(Intent.ACTION_SENDTO,
Jay Shrauner1cd88e32014-09-05 15:37:55 -07001869 Uri.fromParts(ContactsUtils.SCHEME_SMSTO, phone.getNumber(), null));
Paul Soulos48fc9122014-08-26 13:52:36 -07001870
Walter Jang3133a532016-05-04 17:51:19 -07001871 alternateIcon = res.getDrawable(R.drawable.ic_message_24dp_mirrored);
Paul Soulos23e28362014-08-29 14:57:08 -07001872 alternateContentDescription.append(res.getString(R.string.sms_custom, header));
Wenyi Wang5da55ff2015-11-19 13:22:40 -08001873 smsContentDescription = com.android.contacts.common.util.ContactDisplayUtils
1874 .getTelephoneTtsSpannable(alternateContentDescription.toString(), header);
Paul Soulos48fc9122014-08-26 13:52:36 -07001875
Tyler Gunn0a8f9732015-12-15 15:38:54 -08001876 int videoCapability = CallUtil.getVideoCallingAvailability(context);
1877 boolean isPresenceEnabled =
1878 (videoCapability & CallUtil.VIDEO_CALLING_PRESENCE) != 0;
1879 boolean isVideoEnabled = (videoCapability & CallUtil.VIDEO_CALLING_ENABLED) != 0;
1880
Tyler Gunn5f87e922015-08-05 14:24:52 -07001881 if (CallUtil.isCallWithSubjectSupported(context)) {
1882 thirdIcon = res.getDrawable(R.drawable.ic_call_note_white_24dp);
1883 thirdAction = Entry.ACTION_CALL_WITH_SUBJECT;
1884 thirdContentDescription =
1885 res.getString(R.string.call_with_a_note);
Tyler Gunn5f87e922015-08-05 14:24:52 -07001886 // Create a bundle containing the data the call subject dialog requires.
1887 thirdExtras = new Bundle();
1888 thirdExtras.putLong(CallSubjectDialog.ARG_PHOTO_ID,
1889 contactData.getPhotoId());
1890 thirdExtras.putParcelable(CallSubjectDialog.ARG_PHOTO_URI,
1891 UriUtils.parseUriOrNull(contactData.getPhotoUri()));
1892 thirdExtras.putParcelable(CallSubjectDialog.ARG_CONTACT_URI,
1893 contactData.getLookupUri());
1894 thirdExtras.putString(CallSubjectDialog.ARG_NAME_OR_NUMBER,
1895 contactData.getDisplayName());
1896 thirdExtras.putBoolean(CallSubjectDialog.ARG_IS_BUSINESS, false);
1897 thirdExtras.putString(CallSubjectDialog.ARG_NUMBER,
1898 phone.getNumber());
1899 thirdExtras.putString(CallSubjectDialog.ARG_DISPLAY_NUMBER,
1900 phone.getFormattedPhoneNumber());
1901 thirdExtras.putString(CallSubjectDialog.ARG_NUMBER_LABEL,
1902 phoneLabel);
Tyler Gunn0a8f9732015-12-15 15:38:54 -08001903 } else if (isVideoEnabled) {
1904 // Check to ensure carrier presence indicates the number supports video calling.
1905 int carrierPresence = dataItem.getCarrierPresence();
1906 boolean isPresent = (carrierPresence & Phone.CARRIER_PRESENCE_VT_CAPABLE) != 0;
1907
1908 if ((isPresenceEnabled && isPresent) || !isPresenceEnabled) {
1909 thirdIcon = res.getDrawable(R.drawable.ic_videocam);
1910 thirdAction = Entry.ACTION_INTENT;
1911 thirdIntent = CallUtil.getVideoCallIntent(phone.getNumber(),
1912 CALL_ORIGIN_QUICK_CONTACTS_ACTIVITY);
1913 thirdContentDescription =
1914 res.getString(R.string.description_video_call);
1915 }
Paul Soulos48fc9122014-08-26 13:52:36 -07001916 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001917 }
1918 } else if (dataItem instanceof EmailDataItem) {
1919 final EmailDataItem email = (EmailDataItem) dataItem;
1920 final String address = email.getData();
1921 if (!TextUtils.isEmpty(address)) {
Paul Soulos23e28362014-08-29 14:57:08 -07001922 primaryContentDescription.append(res.getString(R.string.email_other)).append(" ");
Jay Shrauner1cd88e32014-09-05 15:37:55 -07001923 final Uri mailUri = Uri.fromParts(ContactsUtils.SCHEME_MAILTO, address, null);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001924 intent = new Intent(Intent.ACTION_SENDTO, mailUri);
1925 header = email.getAddress();
Paul Soulos2a4207f2014-07-31 17:09:05 -07001926 entryContextMenuInfo = new EntryContextMenuInfo(header,
Paul Soulos97f27802014-09-08 13:55:45 -07001927 res.getString(R.string.emailLabelsGroup), dataItem.getMimeType(),
1928 dataItem.getId(), dataItem.isSuperPrimary());
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001929 if (email.hasKindTypeColumn(kind)) {
Paul Soulos23e28362014-08-29 14:57:08 -07001930 text = Email.getTypeLabel(res, email.getKindTypeColumn(kind),
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001931 email.getLabel()).toString();
Paul Soulos23e28362014-08-29 14:57:08 -07001932 primaryContentDescription.append(text).append(" ");
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001933 }
Paul Soulos23e28362014-08-29 14:57:08 -07001934 primaryContentDescription.append(header);
1935 icon = res.getDrawable(R.drawable.ic_email_24dp);
Paul Soulos48290be2014-09-08 13:44:51 -07001936 iconResourceId = R.drawable.ic_email_24dp;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001937 }
1938 } else if (dataItem instanceof StructuredPostalDataItem) {
1939 StructuredPostalDataItem postal = (StructuredPostalDataItem) dataItem;
1940 final String postalAddress = postal.getFormattedAddress();
1941 if (!TextUtils.isEmpty(postalAddress)) {
Paul Soulos23e28362014-08-29 14:57:08 -07001942 primaryContentDescription.append(res.getString(R.string.map_other)).append(" ");
Paul Souloseb64a4b2014-07-07 17:03:27 -07001943 intent = StructuredPostalUtils.getViewPostalAddressIntent(postalAddress);
1944 header = postal.getFormattedAddress();
Paul Soulos2a4207f2014-07-31 17:09:05 -07001945 entryContextMenuInfo = new EntryContextMenuInfo(header,
Paul Soulos97f27802014-09-08 13:55:45 -07001946 res.getString(R.string.postalLabelsGroup), dataItem.getMimeType(),
1947 dataItem.getId(), dataItem.isSuperPrimary());
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001948 if (postal.hasKindTypeColumn(kind)) {
Paul Soulos23e28362014-08-29 14:57:08 -07001949 text = StructuredPostal.getTypeLabel(res,
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001950 postal.getKindTypeColumn(kind), postal.getLabel()).toString();
Paul Soulos23e28362014-08-29 14:57:08 -07001951 primaryContentDescription.append(text).append(" ");
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001952 }
Paul Soulos23e28362014-08-29 14:57:08 -07001953 primaryContentDescription.append(header);
Paul Soulos6a4d2732014-07-22 14:22:12 -07001954 alternateIntent =
1955 StructuredPostalUtils.getViewPostalAddressDirectionsIntent(postalAddress);
Paul Soulos23e28362014-08-29 14:57:08 -07001956 alternateIcon = res.getDrawable(R.drawable.ic_directions_24dp);
1957 alternateContentDescription.append(res.getString(
1958 R.string.content_description_directions)).append(" ").append(header);
1959 icon = res.getDrawable(R.drawable.ic_place_24dp);
Paul Soulos48290be2014-09-08 13:44:51 -07001960 iconResourceId = R.drawable.ic_place_24dp;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001961 }
1962 } else if (dataItem instanceof SipAddressDataItem) {
Brian Attwell593e5a72014-10-22 19:45:28 -07001963 final SipAddressDataItem sip = (SipAddressDataItem) dataItem;
1964 final String address = sip.getSipAddress();
1965 if (!TextUtils.isEmpty(address)) {
1966 primaryContentDescription.append(res.getString(R.string.call_other)).append(
1967 " ");
1968 if (PhoneCapabilityTester.isSipPhone(context)) {
Jay Shrauner1cd88e32014-09-05 15:37:55 -07001969 final Uri callUri = Uri.fromParts(PhoneAccount.SCHEME_SIP, address, null);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001970 intent = CallUtil.getCallIntent(callUri);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001971 }
Brian Attwell593e5a72014-10-22 19:45:28 -07001972 header = address;
1973 entryContextMenuInfo = new EntryContextMenuInfo(header,
1974 res.getString(R.string.phoneLabelsGroup), dataItem.getMimeType(),
1975 dataItem.getId(), dataItem.isSuperPrimary());
1976 if (sip.hasKindTypeColumn(kind)) {
1977 text = SipAddress.getTypeLabel(res,
1978 sip.getKindTypeColumn(kind), sip.getLabel()).toString();
1979 primaryContentDescription.append(text).append(" ");
1980 }
1981 primaryContentDescription.append(header);
1982 icon = res.getDrawable(R.drawable.ic_dialer_sip_black_24dp);
1983 iconResourceId = R.drawable.ic_dialer_sip_black_24dp;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001984 }
1985 } else if (dataItem instanceof StructuredNameDataItem) {
Walter Jang8025f802015-02-27 14:18:14 -08001986 // If the name is already set and this is not the super primary value then leave the
1987 // current value. This way we show the super primary value when we are able to.
1988 if (dataItem.isSuperPrimary() || aboutCardName.value == null
1989 || aboutCardName.value.isEmpty()) {
1990 final String givenName = ((StructuredNameDataItem) dataItem).getGivenName();
1991 if (!TextUtils.isEmpty(givenName)) {
1992 aboutCardName.value = res.getString(R.string.about_card_title) +
1993 " " + givenName;
1994 } else {
1995 aboutCardName.value = res.getString(R.string.about_card_title);
1996 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001997 }
1998 } else {
1999 // Custom DataItem
Brian Attwell6bb01342014-08-20 23:16:00 -07002000 header = dataItem.buildDataStringForDisplay(context, kind);
Paul Souloseb64a4b2014-07-07 17:03:27 -07002001 text = kind.typeColumn;
2002 intent = new Intent(Intent.ACTION_VIEW);
Paul Soulos60e51082014-07-10 12:33:04 -07002003 final Uri uri = ContentUris.withAppendedId(Data.CONTENT_URI, dataItem.getId());
2004 intent.setDataAndType(uri, dataItem.getMimeType());
Paul Soulose0055662014-07-10 16:33:08 -07002005
2006 if (intent != null) {
2007 final String mimetype = intent.getType();
2008
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002009 // Build advanced entry for known 3p types. Otherwise default to ResolveCache icon.
Paul Soulose0055662014-07-10 16:33:08 -07002010 switch (mimetype) {
2011 case MIMETYPE_GPLUS_PROFILE:
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002012 // If a secondDataItem is available, use it to build an entry with
2013 // alternate actions
2014 if (secondDataItem != null) {
Paul Soulos23e28362014-08-29 14:57:08 -07002015 icon = res.getDrawable(R.drawable.ic_google_plus_24dp);
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002016 alternateIcon = res.getDrawable(R.drawable.ic_add_to_circles_black_24);
2017 final GPlusOrHangoutsDataItemModel itemModel =
2018 new GPlusOrHangoutsDataItemModel(intent, alternateIntent,
2019 dataItem, secondDataItem, alternateContentDescription,
2020 header, text, context);
2021
Paul Soulos97b1e2d2014-10-12 16:56:44 -07002022 populateGPlusOrHangoutsDataItemModel(itemModel);
2023 intent = itemModel.intent;
2024 alternateIntent = itemModel.alternateIntent;
2025 alternateContentDescription = itemModel.alternateContentDescription;
2026 header = itemModel.header;
2027 text = itemModel.text;
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002028 } else {
2029 if (GPLUS_PROFILE_DATA_5_ADD_TO_CIRCLE.equals(
2030 intent.getDataString())) {
2031 icon = res.getDrawable(R.drawable.ic_add_to_circles_black_24);
2032 } else {
2033 icon = res.getDrawable(R.drawable.ic_google_plus_24dp);
2034 }
Paul Soulose0055662014-07-10 16:33:08 -07002035 }
2036 break;
2037 case MIMETYPE_HANGOUTS:
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002038 // If a secondDataItem is available, use it to build an entry with
2039 // alternate actions
2040 if (secondDataItem != null) {
Paul Soulos23e28362014-08-29 14:57:08 -07002041 icon = res.getDrawable(R.drawable.ic_hangout_24dp);
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002042 alternateIcon = res.getDrawable(R.drawable.ic_hangout_video_24dp);
Paul Soulos97b1e2d2014-10-12 16:56:44 -07002043 final GPlusOrHangoutsDataItemModel itemModel =
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002044 new GPlusOrHangoutsDataItemModel(intent, alternateIntent,
2045 dataItem, secondDataItem, alternateContentDescription,
2046 header, text, context);
2047
Paul Soulos97b1e2d2014-10-12 16:56:44 -07002048 populateGPlusOrHangoutsDataItemModel(itemModel);
2049 intent = itemModel.intent;
2050 alternateIntent = itemModel.alternateIntent;
2051 alternateContentDescription = itemModel.alternateContentDescription;
2052 header = itemModel.header;
2053 text = itemModel.text;
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002054 } else {
2055 if (HANGOUTS_DATA_5_VIDEO.equals(intent.getDataString())) {
2056 icon = res.getDrawable(R.drawable.ic_hangout_video_24dp);
2057 } else {
2058 icon = res.getDrawable(R.drawable.ic_hangout_24dp);
2059 }
Paul Soulose0055662014-07-10 16:33:08 -07002060 }
2061 break;
2062 default:
Paul Soulos97f27802014-09-08 13:55:45 -07002063 entryContextMenuInfo = new EntryContextMenuInfo(header, mimetype,
2064 dataItem.getMimeType(), dataItem.getId(),
2065 dataItem.isSuperPrimary());
Brian Attwell6bb01342014-08-20 23:16:00 -07002066 icon = ResolveCache.getInstance(context).getIcon(
Paul Soulose0055662014-07-10 16:33:08 -07002067 dataItem.getMimeType(), intent);
2068 // Call mutate to create a new Drawable.ConstantState for color filtering
2069 if (icon != null) {
2070 icon.mutate();
2071 }
Paul Soulos48ebbaa2014-07-15 13:11:23 -07002072 shouldApplyColor = false;
Paul Soulose0055662014-07-10 16:33:08 -07002073 }
2074 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -07002075 }
2076
Paul Souloseb64a4b2014-07-07 17:03:27 -07002077 if (intent != null) {
2078 // Do not set the intent is there are no resolves
Brian Attwell6bb01342014-08-20 23:16:00 -07002079 if (!PhoneCapabilityTester.isIntentRegistered(context, intent)) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07002080 intent = null;
Paul Soulos16339332014-06-24 13:59:56 -07002081 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07002082 }
Paul Soulos16339332014-06-24 13:59:56 -07002083
Paul Soulosdd7419d2014-07-15 11:22:13 -07002084 if (alternateIntent != null) {
2085 // Do not set the alternate intent is there are no resolves
Brian Attwell6bb01342014-08-20 23:16:00 -07002086 if (!PhoneCapabilityTester.isIntentRegistered(context, alternateIntent)) {
Paul Soulosdd7419d2014-07-15 11:22:13 -07002087 alternateIntent = null;
Jay Shraunerf0d04c12014-09-02 13:44:48 -07002088 } else if (TextUtils.isEmpty(alternateContentDescription)) {
2089 // Attempt to use package manager to find a suitable content description if needed
Paul Soulos23e28362014-08-29 14:57:08 -07002090 alternateContentDescription.append(getIntentResolveLabel(alternateIntent, context));
Paul Soulosdd7419d2014-07-15 11:22:13 -07002091 }
2092 }
2093
Paul Souloseb64a4b2014-07-07 17:03:27 -07002094 // If the Entry has no visual elements, return null
2095 if (icon == null && TextUtils.isEmpty(header) && TextUtils.isEmpty(subHeader) &&
2096 subHeaderIcon == null && TextUtils.isEmpty(text) && textIcon == null) {
2097 return null;
2098 }
2099
Brian Attwellfa238942014-08-12 10:21:28 -07002100 // Ignore dataIds from the Me profile.
Paul Soulosea5e0b72014-07-08 18:09:44 -07002101 final int dataId = dataItem.getId() > Integer.MAX_VALUE ?
2102 -1 : (int) dataItem.getId();
2103
Paul Soulos23e28362014-08-29 14:57:08 -07002104 return new Entry(dataId, icon, header, subHeader, subHeaderIcon, text, textIcon,
Tingting Wangb6949dc2015-11-03 13:17:12 -08002105 phoneContentDescription == null
2106 ? new SpannableString(primaryContentDescription.toString())
2107 : phoneContentDescription,
Walter Jang7ce53522014-10-29 13:26:43 -07002108 intent, alternateIcon, alternateIntent,
Wenyi Wang5da55ff2015-11-19 13:22:40 -08002109 smsContentDescription == null
2110 ? new SpannableString(alternateContentDescription.toString())
2111 : smsContentDescription,
2112 shouldApplyColor, isEditable,
Tyler Gunn5f87e922015-08-05 14:24:52 -07002113 entryContextMenuInfo, thirdIcon, thirdIntent, thirdContentDescription, thirdAction,
2114 thirdExtras, iconResourceId);
Paul Souloseb64a4b2014-07-07 17:03:27 -07002115 }
2116
Brian Attwell6bb01342014-08-20 23:16:00 -07002117 private List<Entry> dataItemsToEntries(List<DataItem> dataItems,
2118 MutableString aboutCardTitleOut) {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002119 // Hangouts and G+ use two data items to create one entry.
2120 if (dataItems.get(0).getMimeType().equals(MIMETYPE_GPLUS_PROFILE) ||
2121 dataItems.get(0).getMimeType().equals(MIMETYPE_HANGOUTS)) {
2122 return gPlusOrHangoutsDataItemsToEntries(dataItems);
2123 } else {
2124 final List<Entry> entries = new ArrayList<>();
2125 for (DataItem dataItem : dataItems) {
2126 final Entry entry = dataItemToEntry(dataItem, /* secondDataItem = */ null,
2127 this, mContactData, aboutCardTitleOut);
2128 if (entry != null) {
2129 entries.add(entry);
2130 }
2131 }
2132 return entries;
2133 }
2134 }
2135
2136 /**
2137 * G+ and Hangout entries are unique in that a single ExpandingEntryCardView.Entry consists
2138 * of two data items. This method attempts to build each entry using the two data items if
2139 * they are available. If there are more or less than two data items, a fall back is used
2140 * and each data item gets its own entry.
2141 */
2142 private List<Entry> gPlusOrHangoutsDataItemsToEntries(List<DataItem> dataItems) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07002143 final List<Entry> entries = new ArrayList<>();
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002144 final Map<Long, List<DataItem>> buckets = new HashMap<>();
2145 // Put the data items into buckets based on the raw contact id
Paul Souloseb64a4b2014-07-07 17:03:27 -07002146 for (DataItem dataItem : dataItems) {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002147 List<DataItem> bucket = buckets.get(dataItem.getRawContactId());
2148 if (bucket == null) {
2149 bucket = new ArrayList<>();
2150 buckets.put(dataItem.getRawContactId(), bucket);
2151 }
2152 bucket.add(dataItem);
2153 }
2154
2155 // Use the buckets to build entries. If a bucket contains two data items, build the special
2156 // entry, otherwise fall back to the normal entry.
2157 for (List<DataItem> bucket : buckets.values()) {
2158 if (bucket.size() == 2) {
2159 // Use the pair to build an entry
2160 final Entry entry = dataItemToEntry(bucket.get(0),
2161 /* secondDataItem = */ bucket.get(1), this, mContactData,
2162 /* aboutCardName = */ null);
2163 if (entry != null) {
2164 entries.add(entry);
2165 }
2166 } else {
2167 for (DataItem dataItem : bucket) {
2168 final Entry entry = dataItemToEntry(dataItem, /* secondDataItem = */ null,
2169 this, mContactData, /* aboutCardName = */ null);
2170 if (entry != null) {
2171 entries.add(entry);
2172 }
2173 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07002174 }
2175 }
2176 return entries;
Daniel Lehmannedb576a2011-07-27 16:45:13 -07002177 }
2178
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002179 /**
2180 * Used for statically passing around G+ or Hangouts data items and entry fields to
2181 * populateGPlusOrHangoutsDataItemModel.
2182 */
2183 private static final class GPlusOrHangoutsDataItemModel {
2184 public Intent intent;
2185 public Intent alternateIntent;
2186 public DataItem dataItem;
2187 public DataItem secondDataItem;
2188 public StringBuilder alternateContentDescription;
2189 public String header;
2190 public String text;
2191 public Context context;
2192
2193 public GPlusOrHangoutsDataItemModel(Intent intent, Intent alternateIntent, DataItem dataItem,
2194 DataItem secondDataItem, StringBuilder alternateContentDescription, String header,
2195 String text, Context context) {
2196 this.intent = intent;
2197 this.alternateIntent = alternateIntent;
2198 this.dataItem = dataItem;
2199 this.secondDataItem = secondDataItem;
2200 this.alternateContentDescription = alternateContentDescription;
2201 this.header = header;
2202 this.text = text;
2203 this.context = context;
2204 }
2205 }
2206
2207 private static void populateGPlusOrHangoutsDataItemModel(
2208 GPlusOrHangoutsDataItemModel dataModel) {
2209 final Intent secondIntent = new Intent(Intent.ACTION_VIEW);
2210 secondIntent.setDataAndType(ContentUris.withAppendedId(Data.CONTENT_URI,
2211 dataModel.secondDataItem.getId()), dataModel.secondDataItem.getMimeType());
2212 // There is no guarantee the order the data items come in. Second
2213 // data item does not necessarily mean it's the alternate.
2214 // Hangouts video and Add to circles should be alternate. Swap if needed
2215 if (HANGOUTS_DATA_5_VIDEO.equals(
2216 dataModel.dataItem.getContentValues().getAsString(Data.DATA5)) ||
2217 GPLUS_PROFILE_DATA_5_ADD_TO_CIRCLE.equals(
2218 dataModel.dataItem.getContentValues().getAsString(Data.DATA5))) {
2219 dataModel.alternateIntent = dataModel.intent;
2220 dataModel.alternateContentDescription = new StringBuilder(dataModel.header);
2221
2222 dataModel.intent = secondIntent;
2223 dataModel.header = dataModel.secondDataItem.buildDataStringForDisplay(dataModel.context,
2224 dataModel.secondDataItem.getDataKind());
2225 dataModel.text = dataModel.secondDataItem.getDataKind().typeColumn;
2226 } else if (HANGOUTS_DATA_5_MESSAGE.equals(
2227 dataModel.dataItem.getContentValues().getAsString(Data.DATA5)) ||
2228 GPLUS_PROFILE_DATA_5_VIEW_PROFILE.equals(
2229 dataModel.dataItem.getContentValues().getAsString(Data.DATA5))) {
2230 dataModel.alternateIntent = secondIntent;
2231 dataModel.alternateContentDescription = new StringBuilder(
2232 dataModel.secondDataItem.buildDataStringForDisplay(dataModel.context,
2233 dataModel.secondDataItem.getDataKind()));
2234 }
2235 }
2236
Brian Attwell6bb01342014-08-20 23:16:00 -07002237 private static String getIntentResolveLabel(Intent intent, Context context) {
2238 final List<ResolveInfo> matches = context.getPackageManager().queryIntentActivities(intent,
Paul Soulosdd7419d2014-07-15 11:22:13 -07002239 PackageManager.MATCH_DEFAULT_ONLY);
2240
2241 // Pick first match, otherwise best found
2242 ResolveInfo bestResolve = null;
2243 final int size = matches.size();
2244 if (size == 1) {
2245 bestResolve = matches.get(0);
2246 } else if (size > 1) {
Brian Attwell6bb01342014-08-20 23:16:00 -07002247 bestResolve = ResolveCache.getInstance(context).getBestResolve(intent, matches);
Paul Soulosdd7419d2014-07-15 11:22:13 -07002248 }
2249
2250 if (bestResolve == null) {
2251 return null;
2252 }
2253
Brian Attwell6bb01342014-08-20 23:16:00 -07002254 return String.valueOf(bestResolve.loadLabel(context.getPackageManager()));
Paul Soulosdd7419d2014-07-15 11:22:13 -07002255 }
2256
Daniel Lehmannedb576a2011-07-27 16:45:13 -07002257 /**
Brian Attwell31b2d422014-06-05 00:14:58 -07002258 * Asynchronously extract the most vibrant color from the PhotoView. Once extracted,
2259 * apply this tint to {@link MultiShrinkScroller}. This operation takes about 20-30ms
2260 * on a Nexus 5.
2261 */
2262 private void extractAndApplyTintFromPhotoViewAsynchronously() {
2263 if (mScroller == null) {
2264 return;
2265 }
2266 final Drawable imageViewDrawable = mPhotoView.getDrawable();
Brian Attwellfaf97392014-07-10 18:32:30 -07002267 new AsyncTask<Void, Void, MaterialPalette>() {
Brian Attwell31b2d422014-06-05 00:14:58 -07002268 @Override
Brian Attwellfaf97392014-07-10 18:32:30 -07002269 protected MaterialPalette doInBackground(Void... params) {
2270
Jay Shrauner929a62f2014-11-13 12:03:50 -08002271 if (imageViewDrawable instanceof BitmapDrawable && mContactData != null
Brian Attwell95c268e2014-08-26 22:04:15 -07002272 && mContactData.getThumbnailPhotoBinaryData() != null
2273 && mContactData.getThumbnailPhotoBinaryData().length > 0) {
2274 // Perform the color analysis on the thumbnail instead of the full sized
2275 // image, so that our results will be as similar as possible to the Bugle
2276 // app.
2277 final Bitmap bitmap = BitmapFactory.decodeByteArray(
2278 mContactData.getThumbnailPhotoBinaryData(), 0,
2279 mContactData.getThumbnailPhotoBinaryData().length);
2280 try {
2281 final int primaryColor = colorFromBitmap(bitmap);
2282 if (primaryColor != 0) {
2283 return mMaterialColorMapUtils.calculatePrimaryAndSecondaryColor(
2284 primaryColor);
2285 }
2286 } finally {
2287 bitmap.recycle();
Brian Attwellfaf97392014-07-10 18:32:30 -07002288 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002289 }
2290 if (imageViewDrawable instanceof LetterTileDrawable) {
Brian Attwellfaf97392014-07-10 18:32:30 -07002291 final int primaryColor = ((LetterTileDrawable) imageViewDrawable).getColor();
Brian Attwella3859ed2014-07-15 13:51:55 -07002292 return mMaterialColorMapUtils.calculatePrimaryAndSecondaryColor(primaryColor);
Brian Attwell31b2d422014-06-05 00:14:58 -07002293 }
Brian Attwella3859ed2014-07-15 13:51:55 -07002294 return MaterialColorMapUtils.getDefaultPrimaryAndSecondaryColors(getResources());
Brian Attwell31b2d422014-06-05 00:14:58 -07002295 }
2296
2297 @Override
Brian Attwellfaf97392014-07-10 18:32:30 -07002298 protected void onPostExecute(MaterialPalette palette) {
2299 super.onPostExecute(palette);
Brian Attwell8571dd32014-06-23 23:29:10 -07002300 if (mHasComputedThemeColor) {
2301 // If we had previously computed a theme color from the contact photo,
2302 // then do not update the theme color. Changing the theme color several
2303 // seconds after QC has started, as a result of an updated/upgraded photo,
2304 // is a jarring experience. On the other hand, changing the theme color after
2305 // a rotation or onNewIntent() is perfectly fine.
2306 return;
2307 }
2308 // Check that the Photo has not changed. If it has changed, the new tint
2309 // color needs to be extracted
2310 if (imageViewDrawable == mPhotoView.getDrawable()) {
2311 mHasComputedThemeColor = true;
Brian Attwellfaf97392014-07-10 18:32:30 -07002312 setThemeColor(palette);
Wenyi Wang1d86a672016-01-28 17:37:10 -08002313 // update color and photo in suggestion card
2314 onAggregationSuggestionChange();
Brian Attwell31b2d422014-06-05 00:14:58 -07002315 }
2316 }
2317 }.execute();
2318 }
2319
Brian Attwellfaf97392014-07-10 18:32:30 -07002320 private void setThemeColor(MaterialPalette palette) {
Brian Attwell9b889e62014-06-23 18:25:32 -07002321 // If the color is invalid, use the predefined default
Brian Attwell8e29faf2015-01-21 10:22:40 -08002322 mColorFilterColor = palette.mPrimaryColor;
2323 mScroller.setHeaderTintColor(mColorFilterColor);
Brian Attwellfaf97392014-07-10 18:32:30 -07002324 mStatusBarColor = palette.mSecondaryColor;
Brian Attwell9b889e62014-06-23 18:25:32 -07002325 updateStatusBarColor();
Brian Attwell8571dd32014-06-23 23:29:10 -07002326
Brian Attwell9b889e62014-06-23 18:25:32 -07002327 mColorFilter =
Brian Attwell8e29faf2015-01-21 10:22:40 -08002328 new PorterDuffColorFilter(mColorFilterColor, PorterDuff.Mode.SRC_ATOP);
2329 mContactCard.setColorAndFilter(mColorFilterColor, mColorFilter);
2330 mRecentCard.setColorAndFilter(mColorFilterColor, mColorFilter);
2331 mAboutCard.setColorAndFilter(mColorFilterColor, mColorFilter);
Wenyi Wang8d02b162016-01-10 12:43:36 -08002332 mSuggestionsCancelButton.setTextColor(mColorFilterColor);
Brian Attwell9b889e62014-06-23 18:25:32 -07002333 }
2334
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002335 private void updateStatusBarColor() {
Wenyi Wang4c3d3e22015-12-17 14:30:02 -08002336 if (mScroller == null || !CompatUtils.isLollipopCompatible()) {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002337 return;
2338 }
2339 final int desiredStatusBarColor;
2340 // Only use a custom status bar color if QuickContacts touches the top of the viewport.
2341 if (mScroller.getScrollNeededToBeFullScreen() <= 0) {
2342 desiredStatusBarColor = mStatusBarColor;
2343 } else {
2344 desiredStatusBarColor = Color.TRANSPARENT;
2345 }
2346 // Animate to the new color.
Brian Attwell847bf2c2014-07-24 13:59:27 -07002347 final ObjectAnimator animation = ObjectAnimator.ofInt(getWindow(), "statusBarColor",
2348 getWindow().getStatusBarColor(), desiredStatusBarColor);
2349 animation.setDuration(ANIMATION_STATUS_BAR_COLOR_CHANGE_DURATION);
2350 animation.setEvaluator(new ArgbEvaluator());
2351 animation.start();
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002352 }
2353
2354 private int colorFromBitmap(Bitmap bitmap) {
2355 // Author of Palette recommends using 24 colors when analyzing profile photos.
2356 final int NUMBER_OF_PALETTE_COLORS = 24;
2357 final Palette palette = Palette.generate(bitmap, NUMBER_OF_PALETTE_COLORS);
Brian Attwella0f20f72014-07-07 11:51:52 -07002358 if (palette != null && palette.getVibrantSwatch() != null) {
2359 return palette.getVibrantSwatch().getRgb();
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002360 }
2361 return 0;
2362 }
2363
Paul Soulosb3054e52014-06-05 16:46:02 -07002364 private List<Entry> contactInteractionsToEntries(List<ContactInteraction> interactions) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07002365 final List<Entry> entries = new ArrayList<>();
Paul Soulosb3054e52014-06-05 16:46:02 -07002366 for (ContactInteraction interaction : interactions) {
Brian Attwelled188282015-02-11 14:12:41 -08002367 if (interaction == null) {
2368 continue;
2369 }
Paul Soulosea5e0b72014-07-08 18:09:44 -07002370 entries.add(new Entry(/* id = */ -1,
2371 interaction.getIcon(this),
Paul Soulosb3054e52014-06-05 16:46:02 -07002372 interaction.getViewHeader(this),
2373 interaction.getViewBody(this),
2374 interaction.getBodyIcon(this),
2375 interaction.getViewFooter(this),
2376 interaction.getFooterIcon(this),
Paul Soulos23e28362014-08-29 14:57:08 -07002377 interaction.getContentDescription(this),
Paul Soulosb3054e52014-06-05 16:46:02 -07002378 interaction.getIntent(),
Paul Soulosdd7419d2014-07-15 11:22:13 -07002379 /* alternateIcon = */ null,
2380 /* alternateIntent = */ null,
2381 /* alternateContentDescription = */ null,
Paul Soulos48ebbaa2014-07-15 13:11:23 -07002382 /* shouldApplyColor = */ true,
Paul Soulos2a4207f2014-07-31 17:09:05 -07002383 /* isEditable = */ false,
Paul Soulos48fc9122014-08-26 13:52:36 -07002384 /* EntryContextMenuInfo = */ null,
2385 /* thirdIcon = */ null,
2386 /* thirdIntent = */ null,
Paul Soulos48290be2014-09-08 13:44:51 -07002387 /* thirdContentDescription = */ null,
Tyler Gunn5f87e922015-08-05 14:24:52 -07002388 /* thirdAction = */ Entry.ACTION_NONE,
2389 /* thirdActionExtras = */ null,
Paul Soulos48290be2014-09-08 13:44:51 -07002390 interaction.getIconResourceId()));
Paul Soulosb3054e52014-06-05 16:46:02 -07002391 }
2392 return entries;
2393 }
2394
Paul Souloseb64a4b2014-07-07 17:03:27 -07002395 private final LoaderCallbacks<Contact> mLoaderContactCallbacks =
Maurice Chu851222a2012-06-21 11:43:08 -07002396 new LoaderCallbacks<Contact>() {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002397 @Override
Maurice Chu851222a2012-06-21 11:43:08 -07002398 public void onLoaderReset(Loader<Contact> loader) {
Paul Soulos405ae402014-07-14 16:16:36 -07002399 mContactData = null;
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002400 }
2401
2402 @Override
Maurice Chu851222a2012-06-21 11:43:08 -07002403 public void onLoadFinished(Loader<Contact> loader, Contact data) {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002404 Trace.beginSection("onLoadFinished()");
Brian Attwell930da3a2014-10-16 21:01:21 -07002405 try {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002406
Brian Attwell930da3a2014-10-16 21:01:21 -07002407 if (isFinishing()) {
2408 return;
2409 }
2410 if (data.isError()) {
Jay Shrauner02ecc3f2014-12-12 11:29:16 -08002411 // This means either the contact is invalid or we had an
2412 // internal error such as an acore crash.
2413 Log.i(TAG, "Failed to load contact: " + ((ContactLoader)loader).getLookupUri());
2414 Toast.makeText(QuickContactActivity.this, R.string.invalidContactMessage,
2415 Toast.LENGTH_LONG).show();
2416 finish();
2417 return;
Brian Attwell930da3a2014-10-16 21:01:21 -07002418 }
2419 if (data.isNotFound()) {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002420 Log.i(TAG, "No contact found: " + ((ContactLoader)loader).getLookupUri());
2421 Toast.makeText(QuickContactActivity.this, R.string.invalidContactMessage,
2422 Toast.LENGTH_LONG).show();
Brian Attwell930da3a2014-10-16 21:01:21 -07002423 finish();
2424 return;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002425 }
Brian Attwell930da3a2014-10-16 21:01:21 -07002426
2427 bindContactData(data);
2428
2429 } finally {
2430 Trace.endSection();
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002431 }
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002432 }
2433
2434 @Override
Maurice Chu851222a2012-06-21 11:43:08 -07002435 public Loader<Contact> onCreateLoader(int id, Bundle args) {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002436 if (mLookupUri == null) {
2437 Log.wtf(TAG, "Lookup uri wasn't initialized. Loader was started too early");
2438 }
Brian Attwelld28851f2014-06-10 13:25:07 -07002439 // Load all contact data. We need loadGroupMetaData=true to determine whether the
2440 // contact is invisible. If it is, we need to display an "Add to Contacts" MenuItem.
Yorke Leeb2b435a2012-11-12 16:47:06 -08002441 return new ContactLoader(getApplicationContext(), mLookupUri,
Brian Attwelld28851f2014-06-10 13:25:07 -07002442 true /*loadGroupMetaData*/, false /*loadInvitableAccountTypes*/,
Brian Attwell8571dd32014-06-23 23:29:10 -07002443 true /*postViewNotification*/, true /*computeFormattedPhoneNumber*/);
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002444 }
2445 };
Paul Soulosb3054e52014-06-05 16:46:02 -07002446
Brian Attwellb7e43642014-06-02 14:33:04 -07002447 @Override
2448 public void onBackPressed() {
2449 if (mScroller != null) {
Brian Attwell8477eaf2014-06-18 15:39:50 -07002450 if (!mIsExitAnimationInProgress) {
2451 mScroller.scrollOffBottom();
2452 }
Brian Attwellb7e43642014-06-02 14:33:04 -07002453 } else {
2454 super.onBackPressed();
2455 }
2456 }
Paul Soulosb3054e52014-06-05 16:46:02 -07002457
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002458 @Override
2459 public void finish() {
2460 super.finish();
2461
2462 // override transitions to skip the standard window animations
2463 overridePendingTransition(0, 0);
2464 }
2465
Paul Souloseb64a4b2014-07-07 17:03:27 -07002466 private final LoaderCallbacks<List<ContactInteraction>> mLoaderInteractionsCallbacks =
Paul Soulosb3054e52014-06-05 16:46:02 -07002467 new LoaderCallbacks<List<ContactInteraction>>() {
2468
2469 @Override
2470 public Loader<List<ContactInteraction>> onCreateLoader(int id, Bundle args) {
Paul Soulosb3054e52014-06-05 16:46:02 -07002471 Loader<List<ContactInteraction>> loader = null;
2472 switch (id) {
2473 case LOADER_SMS_ID:
Paul Soulosb3054e52014-06-05 16:46:02 -07002474 loader = new SmsInteractionsLoader(
2475 QuickContactActivity.this,
Paul Soulosab840442014-06-17 14:08:40 -07002476 args.getStringArray(KEY_LOADER_EXTRA_PHONES),
Paul Soulosb3054e52014-06-05 16:46:02 -07002477 MAX_SMS_RETRIEVE);
2478 break;
Paul Soulos899aa212014-06-11 12:04:43 -07002479 case LOADER_CALENDAR_ID:
Paul Soulosae4cafe2014-07-09 14:11:03 -07002480 final String[] emailsArray = args.getStringArray(KEY_LOADER_EXTRA_EMAILS);
2481 List<String> emailsList = null;
2482 if (emailsArray != null) {
2483 emailsList = Arrays.asList(args.getStringArray(KEY_LOADER_EXTRA_EMAILS));
2484 }
Paul Soulos899aa212014-06-11 12:04:43 -07002485 loader = new CalendarInteractionsLoader(
2486 QuickContactActivity.this,
Paul Soulosae4cafe2014-07-09 14:11:03 -07002487 emailsList,
Paul Soulos899aa212014-06-11 12:04:43 -07002488 MAX_FUTURE_CALENDAR_RETRIEVE,
2489 MAX_PAST_CALENDAR_RETRIEVE,
2490 FUTURE_MILLISECOND_TO_SEARCH_LOCAL_CALENDAR,
2491 PAST_MILLISECOND_TO_SEARCH_LOCAL_CALENDAR);
2492 break;
Paul Soulosab840442014-06-17 14:08:40 -07002493 case LOADER_CALL_LOG_ID:
Paul Soulosab840442014-06-17 14:08:40 -07002494 loader = new CallLogInteractionsLoader(
2495 QuickContactActivity.this,
2496 args.getStringArray(KEY_LOADER_EXTRA_PHONES),
2497 MAX_CALL_LOG_RETRIEVE);
Paul Soulosb3054e52014-06-05 16:46:02 -07002498 }
2499 return loader;
2500 }
2501
2502 @Override
2503 public void onLoadFinished(Loader<List<ContactInteraction>> loader,
2504 List<ContactInteraction> data) {
Paul Soulosb3054e52014-06-05 16:46:02 -07002505 mRecentLoaderResults.put(loader.getId(), data);
2506
2507 if (isAllRecentDataLoaded()) {
2508 bindRecentData();
2509 }
2510 }
2511
2512 @Override
2513 public void onLoaderReset(Loader<List<ContactInteraction>> loader) {
2514 mRecentLoaderResults.remove(loader.getId());
2515 }
Paul Soulosb3054e52014-06-05 16:46:02 -07002516 };
2517
2518 private boolean isAllRecentDataLoaded() {
2519 return mRecentLoaderResults.size() == mRecentLoaderIds.length;
2520 }
2521
2522 private void bindRecentData() {
Paul Souloseb64a4b2014-07-07 17:03:27 -07002523 final List<ContactInteraction> allInteractions = new ArrayList<>();
Brian Attwell30e1ef12014-09-02 15:49:20 -07002524 final List<List<Entry>> interactionsWrapper = new ArrayList<>();
Paul Soulosb3054e52014-06-05 16:46:02 -07002525
Brian Attwelled188282015-02-11 14:12:41 -08002526 // Serialize mRecentLoaderResults into a single list. This should be done on the main
2527 // thread to avoid races against mRecentLoaderResults edits.
2528 for (List<ContactInteraction> loaderInteractions : mRecentLoaderResults.values()) {
2529 allInteractions.addAll(loaderInteractions);
2530 }
2531
Brian Attwell30e1ef12014-09-02 15:49:20 -07002532 mRecentDataTask = new AsyncTask<Void, Void, Void>() {
Paul Soulosb3054e52014-06-05 16:46:02 -07002533 @Override
Brian Attwell30e1ef12014-09-02 15:49:20 -07002534 protected Void doInBackground(Void... params) {
2535 Trace.beginSection("sort recent loader results");
Paul Soulosb3054e52014-06-05 16:46:02 -07002536
Brian Attwell30e1ef12014-09-02 15:49:20 -07002537 // Sort the interactions by most recent
2538 Collections.sort(allInteractions, new Comparator<ContactInteraction>() {
2539 @Override
2540 public int compare(ContactInteraction a, ContactInteraction b) {
Brian Attwelled188282015-02-11 14:12:41 -08002541 if (a == null && b == null) {
2542 return 0;
2543 }
2544 if (a == null) {
2545 return 1;
2546 }
2547 if (b == null) {
2548 return -1;
2549 }
2550 if (a.getInteractionDate() > b.getInteractionDate()) {
2551 return -1;
2552 }
2553 if (a.getInteractionDate() == b.getInteractionDate()) {
2554 return 0;
2555 }
2556 return 1;
Brian Attwell30e1ef12014-09-02 15:49:20 -07002557 }
2558 });
2559
2560 Trace.endSection();
2561 Trace.beginSection("contactInteractionsToEntries");
2562
2563 // Wrap each interaction in its own list so that an icon is displayed for each entry
2564 for (Entry contactInteraction : contactInteractionsToEntries(allInteractions)) {
2565 List<Entry> entryListWrapper = new ArrayList<>(1);
2566 entryListWrapper.add(contactInteraction);
2567 interactionsWrapper.add(entryListWrapper);
2568 }
2569
2570 Trace.endSection();
2571 return null;
2572 }
2573
2574 @Override
2575 protected void onPostExecute(Void aVoid) {
2576 super.onPostExecute(aVoid);
2577 Trace.beginSection("initialize recents card");
2578
2579 if (allInteractions.size() > 0) {
2580 mRecentCard.initialize(interactionsWrapper,
Paul Soulosb3054e52014-06-05 16:46:02 -07002581 /* numInitialVisibleEntries = */ MIN_NUM_COLLAPSED_RECENT_ENTRIES_SHOWN,
Paul Soulosc8e2a912014-07-21 14:52:20 -07002582 /* isExpanded = */ mRecentCard.isExpanded(), /* isAlwaysExpanded = */ false,
Brian Attwell30e1ef12014-09-02 15:49:20 -07002583 mExpandingEntryCardViewListener, mScroller);
2584 mRecentCard.setVisibility(View.VISIBLE);
2585 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07002586
Brian Attwell30e1ef12014-09-02 15:49:20 -07002587 Trace.endSection();
2588
2589 // About card is initialized along with the contact card, but since it appears after
2590 // the recent card in the UI, we hold off until making it visible until the recent
2591 // card is also ready to avoid stuttering.
2592 if (mAboutCard.shouldShow()) {
2593 mAboutCard.setVisibility(View.VISIBLE);
2594 } else {
2595 mAboutCard.setVisibility(View.GONE);
2596 }
2597 mRecentDataTask = null;
2598 }
2599 };
2600 mRecentDataTask.execute();
Paul Soulosb3054e52014-06-05 16:46:02 -07002601 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002602
2603 @Override
2604 protected void onStop() {
2605 super.onStop();
2606
2607 if (mEntriesAndActionsTask != null) {
2608 // Once the activity is stopped, we will no longer want to bind mEntriesAndActionsTask's
2609 // results on the UI thread. In some circumstances Activities are killed without
2610 // onStop() being called. This is not a problem, because in these circumstances
2611 // the entire process will be killed.
2612 mEntriesAndActionsTask.cancel(/* mayInterruptIfRunning = */ false);
2613 }
Brian Attwell30e1ef12014-09-02 15:49:20 -07002614 if (mRecentDataTask != null) {
2615 mRecentDataTask.cancel(/* mayInterruptIfRunning = */ false);
2616 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002617 }
Paul Soulos23889ba2014-06-10 14:55:32 -07002618
Tingting Wange3a7c4f2015-09-24 18:23:18 -07002619 @Override
2620 public void onDestroy() {
2621 super.onDestroy();
2622 if (mAggregationSuggestionEngine != null) {
2623 mAggregationSuggestionEngine.quit();
2624 }
2625 }
2626
Paul Soulos23889ba2014-06-10 14:55:32 -07002627 /**
Brian Attwelld28851f2014-06-10 13:25:07 -07002628 * Returns true if it is possible to edit the current contact.
2629 */
2630 private boolean isContactEditable() {
2631 return mContactData != null && !mContactData.isDirectoryEntry();
2632 }
2633
Brian Attwella41d6d12014-07-31 15:23:13 -07002634 /**
2635 * Returns true if it is possible to share the current contact.
2636 */
2637 private boolean isContactShareable() {
2638 return mContactData != null && !mContactData.isDirectoryEntry();
2639 }
2640
Brian Attwell60953692014-07-11 17:18:46 -07002641 private Intent getEditContactIntent() {
Walter Jang5a7a23b2015-03-06 10:54:26 -08002642 return EditorIntents.createCompactEditContactIntent(
Walter Jang1e8801b2015-03-10 15:57:05 -07002643 mContactData.getLookupUri(),
2644 mHasComputedThemeColor
2645 ? new MaterialPalette(mColorFilterColor, mStatusBarColor) : null,
Wenyi Wangc41a1e52016-01-29 17:01:35 +00002646 mContactData.getPhotoId());
Brian Attwell60953692014-07-11 17:18:46 -07002647 }
2648
2649 private void editContact() {
Paul Soulosa42ef762014-08-20 10:26:10 -07002650 mHasIntentLaunched = true;
Zheng Fuc00a0b52014-09-02 16:44:44 -07002651 mContactLoader.cacheResult();
Brian Attwell60953692014-07-11 17:18:46 -07002652 startActivityForResult(getEditContactIntent(), REQUEST_CODE_CONTACT_EDITOR_ACTIVITY);
Brian Attwelld28851f2014-06-10 13:25:07 -07002653 }
2654
Walter Jang5d2e2622014-11-03 13:20:48 -08002655 private void deleteContact() {
2656 final Uri contactUri = mContactData.getLookupUri();
2657 ContactDeletionInteraction.start(this, contactUri, /* finishActivityWhenDone =*/ true);
2658 }
2659
Brian Attwelld28851f2014-06-10 13:25:07 -07002660 private void toggleStar(MenuItem starredMenuItem) {
2661 // Make sure there is a contact
Brian Attwell63176c92014-08-18 15:14:18 -07002662 if (mContactData != null) {
Brian Attwelld28851f2014-06-10 13:25:07 -07002663 // Read the current starred value from the UI instead of using the last
2664 // loaded state. This allows rapid tapping without writing the same
2665 // value several times
2666 final boolean isStarred = starredMenuItem.isChecked();
2667
2668 // To improve responsiveness, swap out the picture (and tag) in the UI already
Paul Soulos333091a2014-07-22 13:54:41 -07002669 ContactDisplayUtils.configureStarredMenuItem(starredMenuItem,
Brian Attwelld28851f2014-06-10 13:25:07 -07002670 mContactData.isDirectoryEntry(), mContactData.isUserProfile(),
2671 !isStarred);
2672
2673 // Now perform the real save
Paul Souloseb64a4b2014-07-07 17:03:27 -07002674 final Intent intent = ContactSaveService.createSetStarredIntent(
Brian Attwell63176c92014-08-18 15:14:18 -07002675 QuickContactActivity.this, mContactData.getLookupUri(), !isStarred);
Brian Attwelld28851f2014-06-10 13:25:07 -07002676 startService(intent);
Brian Attwell35ccdba2014-07-18 11:43:23 -07002677
2678 final CharSequence accessibilityText = !isStarred
2679 ? getResources().getText(R.string.description_action_menu_add_star)
2680 : getResources().getText(R.string.description_action_menu_remove_star);
2681 // Accessibility actions need to have an associated view. We can't access the MenuItem's
2682 // underlying view, so put this accessibility action on the root view.
2683 mScroller.announceForAccessibility(accessibilityText);
Brian Attwelld28851f2014-06-10 13:25:07 -07002684 }
2685 }
2686
Brian Attwell752cccf2014-06-10 16:24:04 -07002687 private void shareContact() {
2688 final String lookupKey = mContactData.getLookupKey();
Brian Attwell652936f2015-05-27 19:40:36 -07002689 final Uri shareUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey);
Brian Attwell752cccf2014-06-10 16:24:04 -07002690 final Intent intent = new Intent(Intent.ACTION_SEND);
Yorke Lee552ee562015-07-28 15:58:56 -07002691 intent.setType(Contacts.CONTENT_VCARD_TYPE);
Brian Attwell8dcb4ed2015-06-15 15:45:03 -07002692 intent.putExtra(Intent.EXTRA_STREAM, shareUri);
Brian Attwell752cccf2014-06-10 16:24:04 -07002693
2694 // Launch chooser to share contact via
2695 final CharSequence chooseTitle = getText(R.string.share_via);
2696 final Intent chooseIntent = Intent.createChooser(intent, chooseTitle);
2697
2698 try {
Paul Soulosa42ef762014-08-20 10:26:10 -07002699 mHasIntentLaunched = true;
Brian Attwell652936f2015-05-27 19:40:36 -07002700 ImplicitIntentsUtil.startActivityOutsideApp(this, chooseIntent);
Paul Souloseb64a4b2014-07-07 17:03:27 -07002701 } catch (final ActivityNotFoundException ex) {
Brian Attwell752cccf2014-06-10 16:24:04 -07002702 Toast.makeText(this, R.string.share_error, Toast.LENGTH_SHORT).show();
2703 }
2704 }
2705
2706 /**
2707 * Creates a launcher shortcut with the current contact.
2708 */
2709 private void createLauncherShortcutWithContact() {
2710 final ShortcutIntentBuilder builder = new ShortcutIntentBuilder(this,
2711 new OnShortcutIntentCreatedListener() {
2712
2713 @Override
2714 public void onShortcutIntentCreated(Uri uri, Intent shortcutIntent) {
2715 // Broadcast the shortcutIntent to the launcher to create a
2716 // shortcut to this contact
2717 shortcutIntent.setAction(ACTION_INSTALL_SHORTCUT);
2718 QuickContactActivity.this.sendBroadcast(shortcutIntent);
2719
2720 // Send a toast to give feedback to the user that a shortcut to this
2721 // contact was added to the launcher.
guanxiongliuce8fc7b2016-03-28 11:32:32 -07002722 final String displayName = shortcutIntent
2723 .getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
Tingting Wang168331d2015-10-30 12:00:57 -07002724 final String toastMessage = TextUtils.isEmpty(displayName)
2725 ? getString(R.string.createContactShortcutSuccessful_NoName)
2726 : getString(R.string.createContactShortcutSuccessful, displayName);
2727 Toast.makeText(QuickContactActivity.this, toastMessage,
Brian Attwell752cccf2014-06-10 16:24:04 -07002728 Toast.LENGTH_SHORT).show();
2729 }
2730
2731 });
Brian Attwell63176c92014-08-18 15:14:18 -07002732 builder.createContactShortcutIntent(mContactData.getLookupUri());
Brian Attwell752cccf2014-06-10 16:24:04 -07002733 }
2734
Brian Attwell66965e12014-09-08 16:15:20 -07002735 private boolean isShortcutCreatable() {
Jay Shrauner1c06ce72015-05-01 11:56:08 -07002736 if (mContactData == null || mContactData.isUserProfile() ||
2737 mContactData.isDirectoryEntry()) {
Brian Attwell8d0557e2014-12-02 11:18:16 -08002738 return false;
2739 }
Brian Attwell66965e12014-09-08 16:15:20 -07002740 final Intent createShortcutIntent = new Intent();
2741 createShortcutIntent.setAction(ACTION_INSTALL_SHORTCUT);
2742 final List<ResolveInfo> receivers = getPackageManager()
2743 .queryBroadcastReceivers(createShortcutIntent, 0);
2744 return receivers != null && receivers.size() > 0;
2745 }
2746
Brian Attwelld28851f2014-06-10 13:25:07 -07002747 @Override
2748 public boolean onCreateOptionsMenu(Menu menu) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07002749 final MenuInflater inflater = getMenuInflater();
Brian Attwelld28851f2014-06-10 13:25:07 -07002750 inflater.inflate(R.menu.quickcontact, menu);
2751 return true;
2752 }
2753
2754 @Override
2755 public boolean onPrepareOptionsMenu(Menu menu) {
2756 if (mContactData != null) {
2757 final MenuItem starredMenuItem = menu.findItem(R.id.menu_star);
Paul Soulos333091a2014-07-22 13:54:41 -07002758 ContactDisplayUtils.configureStarredMenuItem(starredMenuItem,
Brian Attwelld28851f2014-06-10 13:25:07 -07002759 mContactData.isDirectoryEntry(), mContactData.isUserProfile(),
2760 mContactData.getStarred());
Brian Attwella41d6d12014-07-31 15:23:13 -07002761
Brian Attwelld28851f2014-06-10 13:25:07 -07002762 // Configure edit MenuItem
2763 final MenuItem editMenuItem = menu.findItem(R.id.menu_edit);
2764 editMenuItem.setVisible(true);
2765 if (DirectoryContactUtil.isDirectoryContact(mContactData) || InvisibleContactUtil
2766 .isInvisibleAndAddable(mContactData, this)) {
Brian Attwell30cfd122014-06-13 16:42:11 -07002767 editMenuItem.setIcon(R.drawable.ic_person_add_tinted_24dp);
Brian Attwell2e4214c2014-07-10 22:03:16 -07002768 editMenuItem.setTitle(R.string.menu_add_contact);
Brian Attwelld28851f2014-06-10 13:25:07 -07002769 } else if (isContactEditable()) {
2770 editMenuItem.setIcon(R.drawable.ic_create_24dp);
Brian Attwell2e4214c2014-07-10 22:03:16 -07002771 editMenuItem.setTitle(R.string.menu_editContact);
Brian Attwelld28851f2014-06-10 13:25:07 -07002772 } else {
2773 editMenuItem.setVisible(false);
2774 }
Brian Attwella41d6d12014-07-31 15:23:13 -07002775
Walter Jang5d2e2622014-11-03 13:20:48 -08002776 final MenuItem deleteMenuItem = menu.findItem(R.id.menu_delete);
Walter Jang82ed2b52015-09-09 12:01:04 -07002777 deleteMenuItem.setVisible(isContactEditable() && !mContactData.isUserProfile());
Walter Jang5d2e2622014-11-03 13:20:48 -08002778
Brian Attwella41d6d12014-07-31 15:23:13 -07002779 final MenuItem shareMenuItem = menu.findItem(R.id.menu_share);
2780 shareMenuItem.setVisible(isContactShareable());
2781
Brian Attwell66965e12014-09-08 16:15:20 -07002782 final MenuItem shortcutMenuItem = menu.findItem(R.id.menu_create_contact_shortcut);
2783 shortcutMenuItem.setVisible(isShortcutCreatable());
2784
Brian Attwell56bcc2f2015-02-12 23:45:27 +00002785 final MenuItem helpMenu = menu.findItem(R.id.menu_help);
2786 helpMenu.setVisible(HelpUtils.isHelpAndFeedbackAvailable());
2787
Paul Soulos8a6d0022014-06-16 15:11:03 -07002788 return true;
Brian Attwelld28851f2014-06-10 13:25:07 -07002789 }
Paul Soulos8a6d0022014-06-16 15:11:03 -07002790 return false;
Brian Attwelld28851f2014-06-10 13:25:07 -07002791 }
2792
2793 @Override
2794 public boolean onOptionsItemSelected(MenuItem item) {
2795 switch (item.getItemId()) {
2796 case R.id.menu_star:
2797 toggleStar(item);
2798 return true;
2799 case R.id.menu_edit:
2800 if (DirectoryContactUtil.isDirectoryContact(mContactData)) {
Paul Soulosab54ea12014-08-28 17:22:38 -07002801 // This action is used to launch the contact selector, with the option of
2802 // creating a new contact. Creating a new contact is an INSERT, while selecting
2803 // an exisiting one is an edit. The fields in the edit screen will be
2804 // prepopulated with data.
2805
2806 final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
2807 intent.setType(Contacts.CONTENT_ITEM_TYPE);
2808
Paul Soulosab54ea12014-08-28 17:22:38 -07002809 ArrayList<ContentValues> values = mContactData.getContentValues();
Brian Attwellfc423b42014-10-17 12:58:15 -07002810
2811 // Only pre-fill the name field if the provided display name is an nickname
2812 // or better (e.g. structured name, nickname)
2813 if (mContactData.getDisplayNameSource() >= DisplayNameSources.NICKNAME) {
2814 intent.putExtra(Intents.Insert.NAME, mContactData.getDisplayName());
2815 } else if (mContactData.getDisplayNameSource()
2816 == DisplayNameSources.ORGANIZATION) {
2817 // This is probably an organization. Instead of copying the organization
2818 // name into a name entry, copy it into the organization entry. This
2819 // way we will still consider the contact an organization.
2820 final ContentValues organization = new ContentValues();
2821 organization.put(Organization.COMPANY, mContactData.getDisplayName());
2822 organization.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
2823 values.add(organization);
2824 }
2825
Paul Soulosab54ea12014-08-28 17:22:38 -07002826 // Last time used and times used are aggregated values from the usage stat
2827 // table. They need to be removed from data values so the SQL table can insert
2828 // properly
2829 for (ContentValues value : values) {
2830 value.remove(Data.LAST_TIME_USED);
2831 value.remove(Data.TIMES_USED);
2832 }
2833 intent.putExtra(Intents.Insert.DATA, values);
2834
2835 // If the contact can only export to the same account, add it to the intent.
2836 // Otherwise the ContactEditorFragment will show a dialog for selecting an
2837 // account.
2838 if (mContactData.getDirectoryExportSupport() ==
2839 Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY) {
Brian Attwell4a1c5742015-01-26 15:59:58 -08002840 intent.putExtra(Intents.Insert.EXTRA_ACCOUNT,
Paul Soulosab54ea12014-08-28 17:22:38 -07002841 new Account(mContactData.getDirectoryAccountName(),
2842 mContactData.getDirectoryAccountType()));
Brian Attwell4a1c5742015-01-26 15:59:58 -08002843 intent.putExtra(Intents.Insert.EXTRA_DATA_SET,
Paul Soulosab54ea12014-08-28 17:22:38 -07002844 mContactData.getRawContacts().get(0).getDataSet());
2845 }
2846
Paul Soulosf19dda92014-09-15 15:48:02 -07002847 // Add this flag to disable the delete menu option on directory contact joins
2848 // with local contacts. The delete option is ambiguous when joining contacts.
2849 intent.putExtra(ContactEditorFragment.INTENT_EXTRA_DISABLE_DELETE_MENU_OPTION,
2850 true);
2851
Paul Soulosab54ea12014-08-28 17:22:38 -07002852 startActivityForResult(intent, REQUEST_CODE_CONTACT_SELECTION_ACTIVITY);
Brian Attwelld28851f2014-06-10 13:25:07 -07002853 } else if (InvisibleContactUtil.isInvisibleAndAddable(mContactData, this)) {
2854 InvisibleContactUtil.addToDefaultGroup(mContactData, this);
2855 } else if (isContactEditable()) {
2856 editContact();
2857 }
2858 return true;
Walter Jang5d2e2622014-11-03 13:20:48 -08002859 case R.id.menu_delete:
Jay Shrauner1c06ce72015-05-01 11:56:08 -07002860 if (isContactEditable()) {
2861 deleteContact();
2862 }
Walter Jang5d2e2622014-11-03 13:20:48 -08002863 return true;
Brian Attwell752cccf2014-06-10 16:24:04 -07002864 case R.id.menu_share:
Jay Shrauner49de62f2014-11-26 10:44:28 -08002865 if (isContactShareable()) {
2866 shareContact();
2867 }
Brian Attwell752cccf2014-06-10 16:24:04 -07002868 return true;
2869 case R.id.menu_create_contact_shortcut:
Jay Shrauner1c06ce72015-05-01 11:56:08 -07002870 if (isShortcutCreatable()) {
2871 createLauncherShortcutWithContact();
2872 }
Brian Attwell752cccf2014-06-10 16:24:04 -07002873 return true;
Brian Attwell56bcc2f2015-02-12 23:45:27 +00002874 case R.id.menu_help:
2875 HelpUtils.launchHelpAndFeedbackForContactScreen(this);
2876 return true;
Brian Attwelld28851f2014-06-10 13:25:07 -07002877 default:
2878 return super.onOptionsItemSelected(item);
2879 }
Paul Soulos23889ba2014-06-10 14:55:32 -07002880 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -07002881}