blob: 73c3c78b23eb1816ea4ac94c6d206407dfbd9e6e [file] [log] [blame]
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001/*
yaolu79525d02016-08-24 12:08:39 -07002
Daniel Lehmannedb576a2011-07-27 16:45:13 -07003 * Copyright (C) 2009 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package com.android.contacts.quickcontact;
19
Walter Jang0e723d92016-07-07 19:00:30 -070020import android.Manifest;
Paul Soulosab54ea12014-08-28 17:22:38 -070021import android.accounts.Account;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -070022import android.animation.ArgbEvaluator;
Brian Attwellb7e43642014-06-02 14:33:04 -070023import android.animation.ObjectAnimator;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070024import android.app.Activity;
Brian Attwelld28851f2014-06-10 13:25:07 -070025import android.app.Fragment;
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +020026import android.app.LoaderManager.LoaderCallbacks;
Paul Souloseb64a4b2014-07-07 17:03:27 -070027import android.app.SearchManager;
Brian Attwell752cccf2014-06-10 16:24:04 -070028import android.content.ActivityNotFoundException;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070029import android.content.ContentUris;
Paul Soulosea5e0b72014-07-08 18:09:44 -070030import android.content.ContentValues;
Brian Attwell6bb01342014-08-20 23:16:00 -070031import android.content.Context;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070032import android.content.Intent;
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +020033import android.content.Loader;
Paul Soulosdd7419d2014-07-15 11:22:13 -070034import android.content.pm.PackageManager;
35import android.content.pm.ResolveInfo;
Wenyi Wang8d02b162016-01-10 12:43:36 -080036import android.content.res.ColorStateList;
Paul Soulos23e28362014-08-29 14:57:08 -070037import android.content.res.Resources;
Brian Attwell31b2d422014-06-05 00:14:58 -070038import android.graphics.Bitmap;
Brian Attwell95c268e2014-08-26 22:04:15 -070039import android.graphics.BitmapFactory;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -070040import android.graphics.Color;
Paul Souloseb64a4b2014-07-07 17:03:27 -070041import android.graphics.PorterDuff;
42import android.graphics.PorterDuffColorFilter;
Brian Attwell31b2d422014-06-05 00:14:58 -070043import android.graphics.drawable.BitmapDrawable;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -070044import android.graphics.drawable.ColorDrawable;
Brian Attwell31b2d422014-06-05 00:14:58 -070045import android.graphics.drawable.Drawable;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070046import android.net.Uri;
Brian Attwell31b2d422014-06-05 00:14:58 -070047import android.os.AsyncTask;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070048import android.os.Bundle;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -070049import android.os.Trace;
Paul Souloseb64a4b2014-07-07 17:03:27 -070050import android.provider.CalendarContract;
Tingting Wange3a7c4f2015-09-24 18:23:18 -070051import android.provider.ContactsContract;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070052import android.provider.ContactsContract.CommonDataKinds.Email;
Paul Souloseb64a4b2014-07-07 17:03:27 -070053import android.provider.ContactsContract.CommonDataKinds.Event;
54import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
55import android.provider.ContactsContract.CommonDataKinds.Identity;
56import android.provider.ContactsContract.CommonDataKinds.Im;
57import android.provider.ContactsContract.CommonDataKinds.Nickname;
58import android.provider.ContactsContract.CommonDataKinds.Note;
59import android.provider.ContactsContract.CommonDataKinds.Organization;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070060import android.provider.ContactsContract.CommonDataKinds.Phone;
Paul Souloseb64a4b2014-07-07 17:03:27 -070061import android.provider.ContactsContract.CommonDataKinds.Relation;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070062import android.provider.ContactsContract.CommonDataKinds.SipAddress;
63import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
64import android.provider.ContactsContract.CommonDataKinds.Website;
Brian Attwell752cccf2014-06-10 16:24:04 -070065import android.provider.ContactsContract.Contacts;
Paul Soulos60e51082014-07-10 12:33:04 -070066import android.provider.ContactsContract.Data;
Paul Soulosab54ea12014-08-28 17:22:38 -070067import android.provider.ContactsContract.Directory;
Paul Souloseb64a4b2014-07-07 17:03:27 -070068import android.provider.ContactsContract.DisplayNameSources;
Paul Soulosea5e0b72014-07-08 18:09:44 -070069import android.provider.ContactsContract.DataUsageFeedback;
Paul Soulosab54ea12014-08-28 17:22:38 -070070import android.provider.ContactsContract.Intents;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070071import android.provider.ContactsContract.QuickContact;
72import android.provider.ContactsContract.RawContacts;
guanxiongliu8e56edf2016-07-14 13:48:55 -070073import android.support.graphics.drawable.VectorDrawableCompat;
guanxiongliu04a19362016-05-18 14:38:51 -070074import android.support.v4.app.ActivityCompat;
Wenyi Wang23d9b6e2015-11-19 15:09:06 -080075import android.support.v4.content.ContextCompat;
Brian Attwell31b2d422014-06-05 00:14:58 -070076import android.support.v7.graphics.Palette;
Tingting Wange3a7c4f2015-09-24 18:23:18 -070077import android.support.v7.widget.CardView;
Tyler Gunn03192222014-09-10 15:20:09 -070078import android.telecom.PhoneAccount;
79import android.telecom.TelecomManager;
Brian Attwellc62cc792014-10-02 12:35:07 -070080import android.text.BidiFormatter;
Tingting Wangb6949dc2015-11-03 13:17:12 -080081import android.text.Spannable;
Walter Jang7ce53522014-10-29 13:26:43 -070082import android.text.SpannableString;
Brian Attwellc62cc792014-10-02 12:35:07 -070083import android.text.TextDirectionHeuristics;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070084import android.text.TextUtils;
85import android.util.Log;
Paul Soulos2a4207f2014-07-31 17:09:05 -070086import android.view.ContextMenu;
87import android.view.ContextMenu.ContextMenuInfo;
Tingting Wange3a7c4f2015-09-24 18:23:18 -070088import android.view.LayoutInflater;
Brian Attwelld28851f2014-06-10 13:25:07 -070089import android.view.Menu;
Brian Attwelld28851f2014-06-10 13:25:07 -070090import android.view.MenuInflater;
Paul Souloseb64a4b2014-07-07 17:03:27 -070091import android.view.MenuItem;
Nancy Chenf619e432014-08-18 20:15:12 -070092import android.view.MotionEvent;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070093import android.view.View;
94import android.view.View.OnClickListener;
Paul Soulos2a4207f2014-07-31 17:09:05 -070095import android.view.View.OnCreateContextMenuListener;
Daisuke Miyakawa10d7df72011-07-29 16:11:05 -070096import android.view.WindowManager;
guanxiongliubcae18d2016-02-26 11:04:19 -080097import android.view.accessibility.AccessibilityEvent;
Tingting Wange3a7c4f2015-09-24 18:23:18 -070098import android.widget.Button;
99import android.widget.CheckBox;
100import android.widget.ImageView;
101import android.widget.LinearLayout;
102import android.widget.TextView;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700103import android.widget.Toast;
Brian Attwelld28851f2014-06-10 13:25:07 -0700104import android.widget.Toolbar;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700105
Christine Chen72b3ab12013-08-13 23:22:34 +0200106import com.android.contacts.ContactSaveService;
Brian Attwelld28851f2014-06-10 13:25:07 -0700107import com.android.contacts.ContactsActivity;
Brian Attwell2d150da2014-07-09 22:35:56 -0700108import com.android.contacts.NfcHandler;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -0700109import com.android.contacts.R;
Wenyi Wangd1cb9042015-11-05 17:17:59 -0800110import com.android.contacts.activities.ContactEditorBaseActivity;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700111import com.android.contacts.common.CallUtil;
Paul Soulos2a4207f2014-07-31 17:09:05 -0700112import com.android.contacts.common.ClipboardUtils;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700113import com.android.contacts.common.Collapser;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700114import com.android.contacts.common.ContactPhotoManager;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700115import com.android.contacts.common.ContactsUtils;
Walter Jangf6236372016-02-22 21:24:22 +0000116import com.android.contacts.common.activity.RequestDesiredPermissionsActivity;
Brian Attwellbdd32642015-05-08 17:03:15 -0700117import com.android.contacts.common.activity.RequestPermissionsActivity;
Wenyi Wang4c3d3e22015-12-17 14:30:02 -0800118import com.android.contacts.common.compat.CompatUtils;
Wenyi Wangc85af282015-12-21 15:55:32 -0800119import com.android.contacts.common.compat.EventCompat;
Wenyi Wangcaf26192016-05-09 15:00:25 -0700120import com.android.contacts.common.compat.MultiWindowCompat;
Tyler Gunn5f87e922015-08-05 14:24:52 -0700121import com.android.contacts.common.dialog.CallSubjectDialog;
Brian Attwelld28851f2014-06-10 13:25:07 -0700122import com.android.contacts.common.editor.SelectAccountDialogFragment;
Nancy Chen70e3f4f2014-08-13 16:04:43 -0700123import com.android.contacts.common.interactions.TouchPointManager;
Brian Attwell31b2d422014-06-05 00:14:58 -0700124import com.android.contacts.common.lettertiles.LetterTileDrawable;
Brian Attwell752cccf2014-06-10 16:24:04 -0700125import com.android.contacts.common.list.ShortcutIntentBuilder;
126import com.android.contacts.common.list.ShortcutIntentBuilder.OnShortcutIntentCreatedListener;
Walter Jang62a20742016-02-24 17:04:12 -0800127import com.android.contacts.common.logging.Logger;
128import com.android.contacts.common.logging.ScreenEvent.ScreenType;
yaolu79525d02016-08-24 12:08:39 -0700129import com.android.contacts.common.logging.QuickContactEvent.ContactType;
130import com.android.contacts.common.logging.QuickContactEvent.CardType;
131import com.android.contacts.common.logging.QuickContactEvent.ActionType;
Chiao Cheng0d5588d2012-11-26 15:34:14 -0800132import com.android.contacts.common.model.AccountTypeManager;
Yorke Leecd321f62013-10-28 15:20:15 -0700133import com.android.contacts.common.model.Contact;
134import com.android.contacts.common.model.ContactLoader;
135import com.android.contacts.common.model.RawContact;
Chiao Cheng428f0082012-11-13 18:38:56 -0800136import com.android.contacts.common.model.account.AccountType;
Brian Attwelld28851f2014-06-10 13:25:07 -0700137import com.android.contacts.common.model.account.AccountWithDataSet;
Yorke Leecd321f62013-10-28 15:20:15 -0700138import com.android.contacts.common.model.dataitem.DataItem;
Chiao Cheng428f0082012-11-13 18:38:56 -0800139import com.android.contacts.common.model.dataitem.DataKind;
Yorke Leecd321f62013-10-28 15:20:15 -0700140import com.android.contacts.common.model.dataitem.EmailDataItem;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700141import com.android.contacts.common.model.dataitem.EventDataItem;
Yorke Leecd321f62013-10-28 15:20:15 -0700142import com.android.contacts.common.model.dataitem.ImDataItem;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700143import com.android.contacts.common.model.dataitem.NicknameDataItem;
144import com.android.contacts.common.model.dataitem.NoteDataItem;
145import com.android.contacts.common.model.dataitem.OrganizationDataItem;
Paul Soulosb3054e52014-06-05 16:46:02 -0700146import com.android.contacts.common.model.dataitem.PhoneDataItem;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700147import com.android.contacts.common.model.dataitem.RelationDataItem;
148import com.android.contacts.common.model.dataitem.SipAddressDataItem;
149import com.android.contacts.common.model.dataitem.StructuredNameDataItem;
150import com.android.contacts.common.model.dataitem.StructuredPostalDataItem;
151import com.android.contacts.common.model.dataitem.WebsiteDataItem;
yaolu1bd88262016-08-18 10:29:12 -0700152import com.android.contacts.common.model.dataitem.CustomDataItem;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700153import com.android.contacts.common.model.ValuesDelta;
Brian Attwellc6100ff2015-02-19 21:35:36 -0800154import com.android.contacts.common.util.ImplicitIntentsUtil;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700155import com.android.contacts.common.util.DateUtils;
Brian Attwellfaf97392014-07-10 18:32:30 -0700156import com.android.contacts.common.util.MaterialColorMapUtils;
157import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
guanxiongliu04a19362016-05-18 14:38:51 -0700158import com.android.contacts.common.util.PermissionsUtil;
Tyler Gunn5f87e922015-08-05 14:24:52 -0700159import com.android.contacts.common.util.UriUtils;
Brian Attwelld41ab8a2014-08-07 11:08:55 -0700160import com.android.contacts.common.util.ViewUtil;
Paul Soulos333091a2014-07-22 13:54:41 -0700161import com.android.contacts.detail.ContactDisplayUtils;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700162import com.android.contacts.editor.AggregationSuggestionEngine;
163import com.android.contacts.editor.AggregationSuggestionEngine.Suggestion;
Paul Soulosf19dda92014-09-15 15:48:02 -0700164import com.android.contacts.editor.ContactEditorFragment;
Walter Jang5a7a23b2015-03-06 10:54:26 -0800165import com.android.contacts.editor.EditorIntents;
Paul Soulos899aa212014-06-11 12:04:43 -0700166import com.android.contacts.interactions.CalendarInteractionsLoader;
Paul Soulosab840442014-06-17 14:08:40 -0700167import com.android.contacts.interactions.CallLogInteractionsLoader;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700168import com.android.contacts.interactions.ContactDeletionInteraction;
Paul Soulosb3054e52014-06-05 16:46:02 -0700169import com.android.contacts.interactions.ContactInteraction;
Paul Soulosb3054e52014-06-05 16:46:02 -0700170import com.android.contacts.interactions.SmsInteractionsLoader;
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700171import com.android.contacts.quickcontact.ExpandingEntryCardView.Entry;
Paul Soulos2a4207f2014-07-31 17:09:05 -0700172import com.android.contacts.quickcontact.ExpandingEntryCardView.EntryContextMenuInfo;
Paul Soulos2ed2a732014-08-12 11:58:39 -0700173import com.android.contacts.quickcontact.ExpandingEntryCardView.EntryTag;
Brian Attwelle8ce6ee2014-06-27 18:26:32 -0700174import com.android.contacts.quickcontact.ExpandingEntryCardView.ExpandingEntryCardViewListener;
Brian Attwell493f8d12014-12-18 12:42:03 -0800175import com.android.contacts.quickcontact.WebAddress.ParseException;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -0700176import com.android.contacts.util.ImageViewDrawableSetter;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700177import com.android.contacts.util.PhoneCapabilityTester;
Brian Attwellb7e43642014-06-02 14:33:04 -0700178import com.android.contacts.util.SchedulingUtils;
Tingting Wangac9596e2016-08-02 22:24:24 -0700179import com.android.contacts.util.SharedPreferenceUtil;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700180import com.android.contacts.util.StructuredPostalUtils;
Brian Attwellb7e43642014-06-02 14:33:04 -0700181import com.android.contacts.widget.MultiShrinkScroller;
182import com.android.contacts.widget.MultiShrinkScroller.MultiShrinkScrollerListener;
Brian Attwell02eaf112014-08-27 21:03:42 -0700183import com.android.contacts.widget.QuickContactImageView;
Brian Attwell56bcc2f2015-02-12 23:45:27 +0000184import com.android.contactsbind.HelpUtils;
185
Chiao Chenge0b2f1e2012-06-12 13:07:56 -0700186import com.google.common.collect.Lists;
187
Jay Shrauner82a5a352014-11-24 11:37:44 -0800188import java.lang.SecurityException;
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700189import java.util.ArrayList;
Paul Soulos899aa212014-06-11 12:04:43 -0700190import java.util.Arrays;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700191import java.util.Calendar;
Paul Soulosb3054e52014-06-05 16:46:02 -0700192import java.util.Collections;
193import java.util.Comparator;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700194import java.util.Date;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700195import java.util.HashMap;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700196import java.util.HashSet;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700197import java.util.List;
Paul Soulosb3054e52014-06-05 16:46:02 -0700198import java.util.Map;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700199import java.util.Set;
200import java.util.TreeSet;
Jay Shrauner12ac1e62014-12-08 15:57:19 -0800201import java.util.concurrent.ConcurrentHashMap;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700202
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700203/**
204 * Mostly translucent {@link Activity} that shows QuickContact dialog. It loads
205 * data asynchronously, and then shows a popup with details centered around
206 * {@link Intent#getSourceBounds()}.
207 */
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700208public class QuickContactActivity extends ContactsActivity
Cody Thomasf32dc992016-05-11 10:56:04 -0700209 implements AggregationSuggestionEngine.Listener {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700210
211 /**
212 * QuickContacts immediately takes up the full screen. All possible information is shown.
213 * This value for {@link android.provider.ContactsContract.QuickContact#EXTRA_MODE}
214 * should only be used by the Contacts app.
215 */
216 public static final int MODE_FULLY_EXPANDED = 4;
217
Walter Jang62a20742016-02-24 17:04:12 -0800218 /** Used to pass the screen where the user came before launching this Activity. */
219 public static final String EXTRA_PREVIOUS_SCREEN_TYPE = "previous_screen_type";
yaolu79525d02016-08-24 12:08:39 -0700220 /** Used to pass the Contact card action. */
221 public static final String EXTRA_ACTION_TYPE = "action_type";
222 public static final String EXTRA_THIRD_PARTY_ACTION = "third_party_action";
223
Gary Mai250fc162016-06-10 16:54:15 -0700224 /** Used to tell the QuickContact that the previous contact was edited, so it can return an
225 * activity result back to the original Activity that launched it. */
226 public static final String EXTRA_CONTACT_EDITED = "contact_edited";
Walter Jang62a20742016-02-24 17:04:12 -0800227
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700228 private static final String TAG = "QuickContact";
229
Brian Attwell9b889e62014-06-23 18:25:32 -0700230 private static final String KEY_THEME_COLOR = "theme_color";
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700231 private static final String KEY_IS_SUGGESTION_LIST_COLLAPSED = "is_suggestion_list_collapsed";
232 private static final String KEY_SELECTED_SUGGESTION_CONTACTS = "selected_suggestion_contacts";
Tingting Wang18906c02015-10-16 14:53:10 -0700233 private static final String KEY_PREVIOUS_CONTACT_ID = "previous_contact_id";
234 private static final String KEY_SUGGESTIONS_AUTO_SELECTED = "suggestions_auto_seleted";
Brian Attwell9b889e62014-06-23 18:25:32 -0700235
Brian Attwell9b889e62014-06-23 18:25:32 -0700236 private static final int ANIMATION_STATUS_BAR_COLOR_CHANGE_DURATION = 150;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700237 private static final int REQUEST_CODE_CONTACT_EDITOR_ACTIVITY = 1;
Brian Attwell0b267fe2014-10-24 19:24:40 +0000238 private static final int SCRIM_COLOR = Color.argb(0xC8, 0, 0, 0);
Paul Soulosab54ea12014-08-28 17:22:38 -0700239 private static final int REQUEST_CODE_CONTACT_SELECTION_ACTIVITY = 2;
Paul Soulosea5e0b72014-07-08 18:09:44 -0700240 private static final String MIMETYPE_SMS = "vnd.android-dir/mms-sms";
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700241
Brian Attwell752cccf2014-06-10 16:24:04 -0700242 /** This is the Intent action to install a shortcut in the launcher. */
243 private static final String ACTION_INSTALL_SHORTCUT =
244 "com.android.launcher.action.INSTALL_SHORTCUT";
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700245
246 @SuppressWarnings("deprecation")
247 private static final String LEGACY_AUTHORITY = android.provider.Contacts.AUTHORITY;
248
Paul Soulose0055662014-07-10 16:33:08 -0700249 private static final String MIMETYPE_GPLUS_PROFILE =
250 "vnd.android.cursor.item/vnd.googleplus.profile";
Paul Soulos6a2a1a72014-10-10 15:52:56 -0700251 private static final String GPLUS_PROFILE_DATA_5_VIEW_PROFILE = "view";
Paul Soulose0055662014-07-10 16:33:08 -0700252 private static final String MIMETYPE_HANGOUTS =
253 "vnd.android.cursor.item/vnd.googleplus.profile.comm";
Paul Soulos6a2a1a72014-10-10 15:52:56 -0700254 private static final String HANGOUTS_DATA_5_VIDEO = "hangout";
255 private static final String HANGOUTS_DATA_5_MESSAGE = "conversation";
Paul Soulos48fc9122014-08-26 13:52:36 -0700256 private static final String CALL_ORIGIN_QUICK_CONTACTS_ACTIVITY =
257 "com.android.contacts.quickcontact.QuickContactActivity";
Paul Soulose0055662014-07-10 16:33:08 -0700258
yaolu79525d02016-08-24 12:08:39 -0700259 // Set true in {@link #onCreate} after orientation change for later use in processIntent().
260 private boolean mIsRecreatedInstance;
261
262 private boolean mShouldLog;
263
264 // Used to store and log the referrer package name and the contact type.
265 private String mReferrer;
266 private int mContactType;
267
Brian Attwell63176c92014-08-18 15:14:18 -0700268 /**
269 * The URI used to load the the Contact. Once the contact is loaded, use Contact#getLookupUri()
270 * instead of referencing this URI.
271 */
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700272 private Uri mLookupUri;
273 private String[] mExcludeMimes;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700274 private int mExtraMode;
Brian Attwell05287bf2015-02-25 22:24:04 -0800275 private String mExtraPrioritizedMimeType;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700276 private int mStatusBarColor;
277 private boolean mHasAlreadyBeenOpened;
Paul Soulos97f27802014-09-08 13:55:45 -0700278 private boolean mOnlyOnePhoneNumber;
279 private boolean mOnlyOneEmail;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700280
Brian Attwell02eaf112014-08-27 21:03:42 -0700281 private QuickContactImageView mPhotoView;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700282 private ExpandingEntryCardView mContactCard;
Brian Attwell60953692014-07-11 17:18:46 -0700283 private ExpandingEntryCardView mNoContactDetailsCard;
Paul Soulosb3054e52014-06-05 16:46:02 -0700284 private ExpandingEntryCardView mRecentCard;
Paul Souloseb64a4b2014-07-07 17:03:27 -0700285 private ExpandingEntryCardView mAboutCard;
guanxiongliu04a19362016-05-18 14:38:51 -0700286 private ExpandingEntryCardView mPermissionExplanationCard;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700287
288 // Suggestion card.
Gary Maid1d8a7a2016-06-07 14:46:58 -0700289 private boolean mShouldShowSuggestions = false;
Tingting Wang18906c02015-10-16 14:53:10 -0700290 private CardView mCollapsedSuggestionCardView;
291 private CardView mExpandSuggestionCardView;
Gary Maid1d8a7a2016-06-07 14:46:58 -0700292 private View mCollapsedSuggestionHeader;
Tingting Wang18906c02015-10-16 14:53:10 -0700293 private TextView mCollapsedSuggestionCardTitle;
294 private TextView mExpandSuggestionCardTitle;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700295 private ImageView mSuggestionSummaryPhoto;
296 private TextView mSuggestionForName;
Wenyi Wang53684892015-10-23 11:11:16 -0700297 private TextView mSuggestionContactsNumber;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700298 private LinearLayout mSuggestionList;
Tingting Wang18906c02015-10-16 14:53:10 -0700299 private Button mSuggestionsCancelButton;
Tingting Wang5585c6e2015-10-14 11:19:58 -0700300 private Button mSuggestionsLinkButton;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700301 private boolean mIsSuggestionListCollapsed;
Tingting Wang18906c02015-10-16 14:53:10 -0700302 private boolean mSuggestionsShouldAutoSelected = true;
303 private long mPreviousContactId = 0;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700304
guanxiongliu04a19362016-05-18 14:38:51 -0700305 // Permission explanation card.
306 private boolean mShouldShowPermissionExplanation = false;
307 private String mPermissionExplanationCardSubHeader = "";
308
Brian Attwellb7e43642014-06-02 14:33:04 -0700309 private MultiShrinkScroller mScroller;
Brian Attwelld28851f2014-06-10 13:25:07 -0700310 private SelectAccountDialogFragmentListener mSelectAccountFragmentListener;
Brian Attwell6bb01342014-08-20 23:16:00 -0700311 private AsyncTask<Void, Void, Cp2DataCardModel> mEntriesAndActionsTask;
Brian Attwell30e1ef12014-09-02 15:49:20 -0700312 private AsyncTask<Void, Void, Void> mRecentDataTask;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700313
314 private AggregationSuggestionEngine mAggregationSuggestionEngine;
315 private List<Suggestion> mSuggestions;
316
317 private TreeSet<Long> mSelectedAggregationIds = new TreeSet<>();
Brian Attwell6bb01342014-08-20 23:16:00 -0700318 /**
319 * The last copy of Cp2DataCardModel that was passed to {@link #populateContactAndAboutCard}.
320 */
321 private Cp2DataCardModel mCachedCp2DataCardModel;
Brian Attwellf1076dc2014-08-12 21:28:21 -0700322 /**
323 * This scrim's opacity is controlled in two different ways. 1) Before the initial entrance
324 * animation finishes, the opacity is animated by a value animator. This is designed to
325 * distract the user from the length of the initial loading time. 2) After the initial
326 * entrance animation, the opacity is directly related to scroll position.
327 */
Brian Attwell9b889e62014-06-23 18:25:32 -0700328 private ColorDrawable mWindowScrim;
Brian Attwellf1076dc2014-08-12 21:28:21 -0700329 private boolean mIsEntranceAnimationFinished;
Brian Attwella3859ed2014-07-15 13:51:55 -0700330 private MaterialColorMapUtils mMaterialColorMapUtils;
Brian Attwell8477eaf2014-06-18 15:39:50 -0700331 private boolean mIsExitAnimationInProgress;
Brian Attwell8571dd32014-06-23 23:29:10 -0700332 private boolean mHasComputedThemeColor;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700333
Paul Soulosa42ef762014-08-20 10:26:10 -0700334 /**
335 * Used to stop the ExpandingEntry cards from adjusting between an entry click and the intent
336 * being launched.
337 */
338 private boolean mHasIntentLaunched;
339
Yorke Lee6cb6f6f2013-09-26 16:26:03 -0700340 private Contact mContactData;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700341 private ContactLoader mContactLoader;
Brian Attwell9b889e62014-06-23 18:25:32 -0700342 private PorterDuffColorFilter mColorFilter;
Brian Attwell8e29faf2015-01-21 10:22:40 -0800343 private int mColorFilterColor;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700344
Josh Gargus9758a922012-03-08 17:12:42 -0800345 private final ImageViewDrawableSetter mPhotoSetter = new ImageViewDrawableSetter();
346
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700347 /**
Paul Souloscb4fcc72014-07-15 14:08:50 -0700348 * {@link #LEADING_MIMETYPES} is used to sort MIME-types.
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700349 *
350 * <p>The MIME-types in {@link #LEADING_MIMETYPES} appear in the front of the dialog,
351 * in the order specified here.</p>
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700352 */
353 private static final List<String> LEADING_MIMETYPES = Lists.newArrayList(
Paul Souloscb4fcc72014-07-15 14:08:50 -0700354 Phone.CONTENT_ITEM_TYPE, SipAddress.CONTENT_ITEM_TYPE, Email.CONTENT_ITEM_TYPE,
Paul Soulos405ae402014-07-14 16:16:36 -0700355 StructuredPostal.CONTENT_ITEM_TYPE);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700356
Paul Soulosa153dba2014-08-06 13:44:57 -0700357 private static final List<String> SORTED_ABOUT_CARD_MIMETYPES = Lists.newArrayList(
358 Nickname.CONTENT_ITEM_TYPE,
359 // Phonetic name is inserted after nickname if it is available.
360 // No mimetype for phonetic name exists.
361 Website.CONTENT_ITEM_TYPE,
362 Organization.CONTENT_ITEM_TYPE,
363 Event.CONTENT_ITEM_TYPE,
364 Relation.CONTENT_ITEM_TYPE,
365 Im.CONTENT_ITEM_TYPE,
366 GroupMembership.CONTENT_ITEM_TYPE,
367 Identity.CONTENT_ITEM_TYPE,
yaolu1bd88262016-08-18 10:29:12 -0700368 CustomDataItem.MIMETYPE_CUSTOM_FIELD,
Paul Soulosa153dba2014-08-06 13:44:57 -0700369 Note.CONTENT_ITEM_TYPE);
Paul Souloseb64a4b2014-07-07 17:03:27 -0700370
Brian Attwellc62cc792014-10-02 12:35:07 -0700371 private static final BidiFormatter sBidiFormatter = BidiFormatter.getInstance();
372
Paul Soulosb3054e52014-06-05 16:46:02 -0700373 /** Id for the background contact loader */
374 private static final int LOADER_CONTACT_ID = 0;
375
376 /** Id for the background Sms Loader */
377 private static final int LOADER_SMS_ID = 1;
Paul Soulosb3054e52014-06-05 16:46:02 -0700378 private static final int MAX_SMS_RETRIEVE = 3;
Paul Soulosab840442014-06-17 14:08:40 -0700379
380 /** Id for the back Calendar Loader */
Paul Soulos899aa212014-06-11 12:04:43 -0700381 private static final int LOADER_CALENDAR_ID = 2;
Paul Soulosab840442014-06-17 14:08:40 -0700382 private static final String KEY_LOADER_EXTRA_EMAILS =
383 QuickContactActivity.class.getCanonicalName() + ".KEY_LOADER_EXTRA_EMAILS";
Paul Soulos899aa212014-06-11 12:04:43 -0700384 private static final int MAX_PAST_CALENDAR_RETRIEVE = 3;
385 private static final int MAX_FUTURE_CALENDAR_RETRIEVE = 3;
386 private static final long PAST_MILLISECOND_TO_SEARCH_LOCAL_CALENDAR =
Paul Soulos40d7a652014-09-03 13:54:11 -0700387 1L * 24L * 60L * 60L * 1000L /* 1 day */;
Paul Soulos899aa212014-06-11 12:04:43 -0700388 private static final long FUTURE_MILLISECOND_TO_SEARCH_LOCAL_CALENDAR =
Paul Soulos40d7a652014-09-03 13:54:11 -0700389 7L * 24L * 60L * 60L * 1000L /* 7 days */;
Paul Soulosb3054e52014-06-05 16:46:02 -0700390
Paul Soulosab840442014-06-17 14:08:40 -0700391 /** Id for the background Call Log Loader */
392 private static final int LOADER_CALL_LOG_ID = 3;
393 private static final int MAX_CALL_LOG_RETRIEVE = 3;
Brian Attwell60953692014-07-11 17:18:46 -0700394 private static final int MIN_NUM_CONTACT_ENTRIES_SHOWN = 3;
395 private static final int MIN_NUM_COLLAPSED_RECENT_ENTRIES_SHOWN = 3;
396 private static final int CARD_ENTRY_ID_EDIT_CONTACT = -2;
guanxiongliu04a19362016-05-18 14:38:51 -0700397 private static final int CARD_ENTRY_ID_REQUEST_PERMISSION = -3;
guanxiongliuc7a4b9c2016-04-30 20:19:21 -0700398 private static final String KEY_LOADER_EXTRA_PHONES =
399 QuickContactActivity.class.getCanonicalName() + ".KEY_LOADER_EXTRA_PHONES";
400 private static final String KEY_LOADER_EXTRA_SIP_NUMBERS =
401 QuickContactActivity.class.getCanonicalName() + ".KEY_LOADER_EXTRA_SIP_NUMBERS";
Paul Soulosab840442014-06-17 14:08:40 -0700402
403 private static final int[] mRecentLoaderIds = new int[]{
404 LOADER_SMS_ID,
405 LOADER_CALENDAR_ID,
406 LOADER_CALL_LOG_ID};
Jay Shrauner12ac1e62014-12-08 15:57:19 -0800407 /**
408 * ConcurrentHashMap constructor params: 4 is initial table size, 0.9f is
409 * load factor before resizing, 1 means we only expect a single thread to
410 * write to the map so make only a single shard
411 */
412 private Map<Integer, List<ContactInteraction>> mRecentLoaderResults =
413 new ConcurrentHashMap<>(4, 0.9f, 1);
Paul Soulosb3054e52014-06-05 16:46:02 -0700414
Brian Attwelld28851f2014-06-10 13:25:07 -0700415 private static final String FRAGMENT_TAG_SELECT_ACCOUNT = "select_account_fragment";
Yorke Lee6cb6f6f2013-09-26 16:26:03 -0700416
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700417 final OnClickListener mEntryClickHandler = new OnClickListener() {
418 @Override
419 public void onClick(View v) {
Paul Soulos2ed2a732014-08-12 11:58:39 -0700420 final Object entryTagObject = v.getTag();
421 if (entryTagObject == null || !(entryTagObject instanceof EntryTag)) {
422 Log.w(TAG, "EntryTag was not used correctly");
423 return;
424 }
425 final EntryTag entryTag = (EntryTag) entryTagObject;
426 final Intent intent = entryTag.getIntent();
427 final int dataId = entryTag.getId();
428
Brian Attwell60953692014-07-11 17:18:46 -0700429 if (dataId == CARD_ENTRY_ID_EDIT_CONTACT) {
430 editContact();
431 return;
432 }
Paul Soulosea5e0b72014-07-08 18:09:44 -0700433
guanxiongliu04a19362016-05-18 14:38:51 -0700434 if (dataId == CARD_ENTRY_ID_REQUEST_PERMISSION) {
435 finish();
436 RequestDesiredPermissionsActivity.startPermissionActivity(
437 QuickContactActivity.this);
438 return;
439 }
440
LIANGTAO GAO19c83e32015-03-12 13:32:04 -0700441 // Pass the touch point through the intent for use in the InCallUI
442 if (Intent.ACTION_CALL.equals(intent.getAction())) {
443 if (TouchPointManager.getInstance().hasValidPoint()) {
444 Bundle extras = new Bundle();
445 extras.putParcelable(TouchPointManager.TOUCH_POINT,
446 TouchPointManager.getInstance().getPoint());
447 intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
448 }
449 }
450
451 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
452
453 mHasIntentLaunched = true;
454 try {
yaolu79525d02016-08-24 12:08:39 -0700455 final int actionType = intent.getIntExtra(EXTRA_ACTION_TYPE,
456 ActionType.UNKNOWN_ACTION);
457 final String thirdPartyAction = intent.getStringExtra(EXTRA_THIRD_PARTY_ACTION);
458 Logger.logQuickContactEvent(mReferrer, mContactType,
459 CardType.UNKNOWN_CARD, actionType, thirdPartyAction);
Tingting Wang8c434e52016-01-29 16:45:09 -0800460 ImplicitIntentsUtil.startActivityInAppIfPossible(QuickContactActivity.this, intent);
LIANGTAO GAO19c83e32015-03-12 13:32:04 -0700461 } catch (SecurityException ex) {
462 Toast.makeText(QuickContactActivity.this, R.string.missing_app,
463 Toast.LENGTH_SHORT).show();
464 Log.e(TAG, "QuickContacts does not have permission to launch "
465 + intent);
466 } catch (ActivityNotFoundException ex) {
467 Toast.makeText(QuickContactActivity.this, R.string.missing_app,
468 Toast.LENGTH_SHORT).show();
469 }
470
Paul Soulosea5e0b72014-07-08 18:09:44 -0700471 // Default to USAGE_TYPE_CALL. Usage is summed among all types for sorting each data id
472 // so the exact usage type is not necessary in all cases
473 String usageType = DataUsageFeedback.USAGE_TYPE_CALL;
474
Paul Soulos20bdf9d2014-07-28 14:31:54 -0700475 final Uri intentUri = intent.getData();
476 if ((intentUri != null && intentUri.getScheme() != null &&
Jay Shrauner1cd88e32014-09-05 15:37:55 -0700477 intentUri.getScheme().equals(ContactsUtils.SCHEME_SMSTO)) ||
Paul Soulosea5e0b72014-07-08 18:09:44 -0700478 (intent.getType() != null && intent.getType().equals(MIMETYPE_SMS))) {
479 usageType = DataUsageFeedback.USAGE_TYPE_SHORT_TEXT;
480 }
481
482 // Data IDs start at 1 so anything less is invalid
483 if (dataId > 0) {
Paul Soulos20bdf9d2014-07-28 14:31:54 -0700484 final Uri dataUsageUri = DataUsageFeedback.FEEDBACK_URI.buildUpon()
Paul Soulosea5e0b72014-07-08 18:09:44 -0700485 .appendPath(String.valueOf(dataId))
486 .appendQueryParameter(DataUsageFeedback.USAGE_TYPE, usageType)
487 .build();
Jay Shrauner98818822015-07-10 12:54:10 -0700488 try {
489 final boolean successful = getContentResolver().update(
490 dataUsageUri, new ContentValues(), null, null) > 0;
491 if (!successful) {
492 Log.w(TAG, "DataUsageFeedback increment failed");
493 }
494 } catch (SecurityException ex) {
495 Log.w(TAG, "DataUsageFeedback increment failed", ex);
Paul Soulosea5e0b72014-07-08 18:09:44 -0700496 }
497 } else {
498 Log.w(TAG, "Invalid Data ID");
499 }
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700500 }
501 };
502
Brian Attwelle8ce6ee2014-06-27 18:26:32 -0700503 final ExpandingEntryCardViewListener mExpandingEntryCardViewListener
504 = new ExpandingEntryCardViewListener() {
505 @Override
506 public void onCollapse(int heightDelta) {
507 mScroller.prepareForShrinkingScrollChild(heightDelta);
508 }
Paul Soulos0cda9ae2014-07-23 11:27:28 -0700509
510 @Override
Brian Attwell245d3d22015-01-21 09:50:08 -0800511 public void onExpand() {
512 mScroller.setDisableTouchesForSuppressLayout(/* areTouchesDisabled = */ true);
513 }
514
515 @Override
516 public void onExpandDone() {
517 mScroller.setDisableTouchesForSuppressLayout(/* areTouchesDisabled = */ false);
Paul Soulos0cda9ae2014-07-23 11:27:28 -0700518 }
Brian Attwelle8ce6ee2014-06-27 18:26:32 -0700519 };
520
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700521 @Override
522 public void onAggregationSuggestionChange() {
Gary Maid1d8a7a2016-06-07 14:46:58 -0700523 if (!mShouldShowSuggestions) {
524 return;
525 }
Wenyi Wang1d86a672016-01-28 17:37:10 -0800526 if (mAggregationSuggestionEngine == null) {
527 return;
528 }
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700529 mSuggestions = mAggregationSuggestionEngine.getSuggestions();
Tingting Wang18906c02015-10-16 14:53:10 -0700530 mCollapsedSuggestionCardView.setVisibility(View.GONE);
531 mExpandSuggestionCardView.setVisibility(View.GONE);
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700532 mSuggestionList.removeAllViews();
533
Wenyi Wang96efbb82015-10-26 11:36:02 -0700534 if (mContactData == null) {
535 return;
536 }
537
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700538 final String suggestionForName = mContactData.getDisplayName();
539 final int suggestionNumber = mSuggestions.size();
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700540
Tingting Wange29b6942015-09-30 15:21:33 -0700541 if (suggestionNumber <= 0) {
542 mSelectedAggregationIds.clear();
543 return;
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700544 }
Tingting Wange29b6942015-09-30 15:21:33 -0700545
Wenyi Wang53684892015-10-23 11:11:16 -0700546 ContactPhotoManager.DefaultImageRequest
547 request = new ContactPhotoManager.DefaultImageRequest(
548 suggestionForName, mContactData.getLookupKey(), ContactPhotoManager.TYPE_DEFAULT,
549 /* isCircular */ true );
550 final long photoId = mContactData.getPhotoId();
Tingting Wang18906c02015-10-16 14:53:10 -0700551 final byte[] photoBytes = mContactData.getThumbnailPhotoBinaryData();
552 if (photoBytes != null) {
Wenyi Wang53684892015-10-23 11:11:16 -0700553 ContactPhotoManager.getInstance(this).loadThumbnail(mSuggestionSummaryPhoto, photoId,
Wenyi Wang5cf746f2015-10-23 18:27:11 -0700554 /* darkTheme */ false , /* isCircular */ true , request);
Tingting Wange29b6942015-09-30 15:21:33 -0700555 } else {
Wenyi Wang53684892015-10-23 11:11:16 -0700556 ContactPhotoManager.DEFAULT_AVATAR.applyDefaultImage(mSuggestionSummaryPhoto,
557 -1, false, request);
Tingting Wange29b6942015-09-30 15:21:33 -0700558 }
559
Tingting Wang18906c02015-10-16 14:53:10 -0700560 final String suggestionTitle = getResources().getQuantityString(
561 R.plurals.quickcontact_suggestion_card_title, suggestionNumber, suggestionNumber);
562 mCollapsedSuggestionCardTitle.setText(suggestionTitle);
563 mExpandSuggestionCardTitle.setText(suggestionTitle);
564
Tingting Wange29b6942015-09-30 15:21:33 -0700565 mSuggestionForName.setText(suggestionForName);
Wenyi Wang53684892015-10-23 11:11:16 -0700566 final int linkedContactsNumber = mContactData.getRawContacts().size();
567 final String contactsInfo;
Wenyi Wang96efbb82015-10-26 11:36:02 -0700568 final String accountName = mContactData.getRawContacts().get(0).getAccountName();
569 if (linkedContactsNumber == 1 && accountName == null) {
570 mSuggestionContactsNumber.setVisibility(View.INVISIBLE);
571 }
572 if (linkedContactsNumber == 1 && accountName != null) {
Wenyi Wang53684892015-10-23 11:11:16 -0700573 contactsInfo = getResources().getString(R.string.contact_from_account_name,
Wenyi Wang96efbb82015-10-26 11:36:02 -0700574 accountName);
Wenyi Wang53684892015-10-23 11:11:16 -0700575 } else {
576 contactsInfo = getResources().getString(
577 R.string.quickcontact_contacts_number, linkedContactsNumber);
578 }
579 mSuggestionContactsNumber.setText(contactsInfo);
Tingting Wange29b6942015-09-30 15:21:33 -0700580
581 final Set<Long> suggestionContactIds = new HashSet<>();
582 for (Suggestion suggestion : mSuggestions) {
583 mSuggestionList.addView(inflateSuggestionListView(suggestion));
584 suggestionContactIds.add(suggestion.contactId);
585 }
586
Tingting Wang18906c02015-10-16 14:53:10 -0700587 if (mIsSuggestionListCollapsed) {
588 collapseSuggestionList();
589 } else {
590 expandSuggestionList();
591 }
592
Tingting Wange29b6942015-09-30 15:21:33 -0700593 // Remove contact Ids that are not suggestions.
594 final Set<Long> selectedSuggestionIds = com.google.common.collect.Sets.intersection(
595 mSelectedAggregationIds, suggestionContactIds);
596 mSelectedAggregationIds = new TreeSet<>(selectedSuggestionIds);
Wenyi Wang96efbb82015-10-26 11:36:02 -0700597 if (!mSelectedAggregationIds.isEmpty()) {
598 enableLinkButton();
599 }
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700600 }
601
602 private void collapseSuggestionList() {
Tingting Wang18906c02015-10-16 14:53:10 -0700603 mCollapsedSuggestionCardView.setVisibility(View.VISIBLE);
604 mExpandSuggestionCardView.setVisibility(View.GONE);
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700605 mIsSuggestionListCollapsed = true;
606 }
607
608 private void expandSuggestionList() {
Tingting Wang18906c02015-10-16 14:53:10 -0700609 mCollapsedSuggestionCardView.setVisibility(View.GONE);
610 mExpandSuggestionCardView.setVisibility(View.VISIBLE);
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700611 mIsSuggestionListCollapsed = false;
612 }
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700613
Tingting Wang18906c02015-10-16 14:53:10 -0700614 private View inflateSuggestionListView(final Suggestion suggestion) {
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700615 final LayoutInflater layoutInflater = LayoutInflater.from(this);
616 final View suggestionView = layoutInflater.inflate(
617 R.layout.quickcontact_suggestion_contact_item, null);
618
Wenyi Wang53684892015-10-23 11:11:16 -0700619 ContactPhotoManager.DefaultImageRequest
620 request = new ContactPhotoManager.DefaultImageRequest(
621 suggestion.name, suggestion.lookupKey, ContactPhotoManager.TYPE_DEFAULT, /*
622 isCircular */ true);
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700623 final ImageView photo = (ImageView) suggestionView.findViewById(
624 R.id.aggregation_suggestion_photo);
625 if (suggestion.photo != null) {
Wenyi Wang53684892015-10-23 11:11:16 -0700626 ContactPhotoManager.getInstance(this).loadThumbnail(photo, suggestion.photoId,
Wenyi Wang5cf746f2015-10-23 18:27:11 -0700627 /* darkTheme */ false, /* isCircular */ true, request);
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700628 } else {
Wenyi Wang53684892015-10-23 11:11:16 -0700629 ContactPhotoManager.DEFAULT_AVATAR.applyDefaultImage(photo, -1, false, request);
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700630 }
631
632 final TextView name = (TextView) suggestionView.findViewById(R.id.aggregation_suggestion_name);
633 name.setText(suggestion.name);
634
Tingting Wang18906c02015-10-16 14:53:10 -0700635 final TextView accountNameView = (TextView) suggestionView.findViewById(
636 R.id.aggregation_suggestion_account_name);
637 final String accountName = suggestion.rawContacts.get(0).accountName;
638 if (!TextUtils.isEmpty(accountName)) {
Wenyi Wang53684892015-10-23 11:11:16 -0700639 accountNameView.setText(
640 getResources().getString(R.string.contact_from_account_name, accountName));
Tingting Wang18906c02015-10-16 14:53:10 -0700641 } else {
Wenyi Wang96efbb82015-10-26 11:36:02 -0700642 accountNameView.setVisibility(View.INVISIBLE);
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700643 }
644
645 final CheckBox checkbox = (CheckBox) suggestionView.findViewById(R.id.suggestion_checkbox);
Wenyi Wang8d02b162016-01-10 12:43:36 -0800646 final int[][] stateSet = new int[][] {
647 new int[] { android.R.attr.state_checked },
648 new int[] { -android.R.attr.state_checked }
649 };
650 final int[] colors = new int[] { mColorFilterColor, mColorFilterColor };
guanxiongliu051315f2016-02-22 15:51:00 -0800651 if (suggestion != null && suggestion.name != null) {
652 checkbox.setContentDescription(suggestion.name + " " +
653 getResources().getString(R.string.contact_from_account_name, accountName));
654 }
Wenyi Wang8d02b162016-01-10 12:43:36 -0800655 checkbox.setButtonTintList(new ColorStateList(stateSet, colors));
Tingting Wang18906c02015-10-16 14:53:10 -0700656 checkbox.setChecked(mSuggestionsShouldAutoSelected ||
657 mSelectedAggregationIds.contains(suggestion.contactId));
658 if (checkbox.isChecked()) {
659 mSelectedAggregationIds.add(suggestion.contactId);
660 }
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700661 checkbox.setTag(suggestion.contactId);
662 checkbox.setOnClickListener(new OnClickListener() {
663 @Override
664 public void onClick(View v) {
665 final CheckBox checkBox = (CheckBox) v;
666 final Long contactId = (Long) checkBox.getTag();
Wenyi Wang53684892015-10-23 11:11:16 -0700667 if (mSelectedAggregationIds.contains(mContactData.getId())) {
668 mSelectedAggregationIds.remove(mContactData.getId());
669 }
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700670 if (checkBox.isChecked()) {
671 mSelectedAggregationIds.add(contactId);
Wenyi Wang53684892015-10-23 11:11:16 -0700672 if (mSelectedAggregationIds.size() >= 1) {
673 enableLinkButton();
674 }
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700675 } else {
676 mSelectedAggregationIds.remove(contactId);
Tingting Wang18906c02015-10-16 14:53:10 -0700677 mSuggestionsShouldAutoSelected = false;
Wenyi Wang53684892015-10-23 11:11:16 -0700678 if (mSelectedAggregationIds.isEmpty()) {
679 disableLinkButton();
680 }
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700681 }
682 }
683 });
Tingting Wang18906c02015-10-16 14:53:10 -0700684
Tingting Wange3a7c4f2015-09-24 18:23:18 -0700685 return suggestionView;
686 }
687
Wenyi Wang53684892015-10-23 11:11:16 -0700688 private void enableLinkButton() {
689 mSuggestionsLinkButton.setClickable(true);
Wenyi Wang8d02b162016-01-10 12:43:36 -0800690 mSuggestionsLinkButton.getBackground().setColorFilter(mColorFilter);
Wenyi Wang56dfa582015-11-17 17:42:44 -0800691 mSuggestionsLinkButton.setTextColor(
Wenyi Wang23d9b6e2015-11-19 15:09:06 -0800692 ContextCompat.getColor(this, android.R.color.white));
Wenyi Wang53684892015-10-23 11:11:16 -0700693 mSuggestionsLinkButton.setOnClickListener(new OnClickListener() {
694 @Override
695 public void onClick(View view) {
696 // Join selected contacts.
697 if (!mSelectedAggregationIds.contains(mContactData.getId())) {
698 mSelectedAggregationIds.add(mContactData.getId());
699 }
Cody Thomasf32dc992016-05-11 10:56:04 -0700700
701 final Long[] contactIdsArray = mSelectedAggregationIds.toArray(
702 new Long[mSelectedAggregationIds.size()]);
703 final long[] contactIdsArray2 = new long[contactIdsArray.length];
704 for (int i = 0; i < contactIdsArray.length; i++) {
705 contactIdsArray2[i] = contactIdsArray[i];
706 }
707
708 final Intent intent = ContactSaveService.createJoinSeveralContactsIntent(
709 QuickContactActivity.this, contactIdsArray2);
710 QuickContactActivity.this.startService(intent);
711
712 disableLinkButton();
Wenyi Wang53684892015-10-23 11:11:16 -0700713 }
714 });
715 }
716
Wenyi Wang53684892015-10-23 11:11:16 -0700717 private void disableLinkButton() {
718 mSuggestionsLinkButton.setClickable(false);
719 mSuggestionsLinkButton.getBackground().setColorFilter(
Wenyi Wang23d9b6e2015-11-19 15:09:06 -0800720 ContextCompat.getColor(this, R.color.disabled_button_background),
Wenyi Wang33f60d32015-10-24 14:44:06 -0700721 PorterDuff.Mode.SRC_ATOP);
Wenyi Wang53684892015-10-23 11:11:16 -0700722 mSuggestionsLinkButton.setTextColor(
Wenyi Wang23d9b6e2015-11-19 15:09:06 -0800723 ContextCompat.getColor(this, R.color.disabled_button_text));
Wenyi Wang53684892015-10-23 11:11:16 -0700724 }
725
Paul Soulos97f27802014-09-08 13:55:45 -0700726 private interface ContextMenuIds {
727 static final int COPY_TEXT = 0;
728 static final int CLEAR_DEFAULT = 1;
729 static final int SET_DEFAULT = 2;
730 }
731
Paul Soulos2a4207f2014-07-31 17:09:05 -0700732 private final OnCreateContextMenuListener mEntryContextMenuListener =
733 new OnCreateContextMenuListener() {
734 @Override
735 public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
736 if (menuInfo == null) {
737 return;
738 }
Paul Soulos97f27802014-09-08 13:55:45 -0700739 final EntryContextMenuInfo info = (EntryContextMenuInfo) menuInfo;
Paul Soulos2a4207f2014-07-31 17:09:05 -0700740 menu.setHeaderTitle(info.getCopyText());
Paul Soulos97f27802014-09-08 13:55:45 -0700741 menu.add(ContextMenu.NONE, ContextMenuIds.COPY_TEXT,
742 ContextMenu.NONE, getString(R.string.copy_text));
743
744 // Don't allow setting or clearing of defaults for non-editable contacts
745 if (!isContactEditable()) {
746 return;
747 }
748
749 final String selectedMimeType = info.getMimeType();
750
751 // Defaults to true will only enable the detail to be copied to the clipboard.
752 boolean onlyOneOfMimeType = true;
753
754 // Only allow primary support for Phone and Email content types
755 if (Phone.CONTENT_ITEM_TYPE.equals(selectedMimeType)) {
756 onlyOneOfMimeType = mOnlyOnePhoneNumber;
757 } else if (Email.CONTENT_ITEM_TYPE.equals(selectedMimeType)) {
758 onlyOneOfMimeType = mOnlyOneEmail;
759 }
760
761 // Checking for previously set default
762 if (info.isSuperPrimary()) {
763 menu.add(ContextMenu.NONE, ContextMenuIds.CLEAR_DEFAULT,
764 ContextMenu.NONE, getString(R.string.clear_default));
765 } else if (!onlyOneOfMimeType) {
766 menu.add(ContextMenu.NONE, ContextMenuIds.SET_DEFAULT,
767 ContextMenu.NONE, getString(R.string.set_default));
768 }
Paul Soulos2a4207f2014-07-31 17:09:05 -0700769 }
770 };
771
772 @Override
773 public boolean onContextItemSelected(MenuItem item) {
774 EntryContextMenuInfo menuInfo;
775 try {
776 menuInfo = (EntryContextMenuInfo) item.getMenuInfo();
777 } catch (ClassCastException e) {
778 Log.e(TAG, "bad menuInfo", e);
779 return false;
780 }
781
Paul Soulos97f27802014-09-08 13:55:45 -0700782 switch (item.getItemId()) {
783 case ContextMenuIds.COPY_TEXT:
784 ClipboardUtils.copyText(this, menuInfo.getCopyLabel(), menuInfo.getCopyText(),
785 true);
786 return true;
787 case ContextMenuIds.SET_DEFAULT:
788 final Intent setIntent = ContactSaveService.createSetSuperPrimaryIntent(this,
789 menuInfo.getId());
790 this.startService(setIntent);
791 return true;
792 case ContextMenuIds.CLEAR_DEFAULT:
793 final Intent clearIntent = ContactSaveService.createClearPrimaryIntent(this,
794 menuInfo.getId());
795 this.startService(clearIntent);
796 return true;
797 default:
798 throw new IllegalArgumentException("Unknown menu option " + item.getItemId());
799 }
Paul Soulos2a4207f2014-07-31 17:09:05 -0700800 }
801
Brian Attwelld28851f2014-06-10 13:25:07 -0700802 /**
803 * Headless fragment used to handle account selection callbacks invoked from
804 * {@link DirectoryContactUtil}.
805 */
806 public static class SelectAccountDialogFragmentListener extends Fragment
807 implements SelectAccountDialogFragment.Listener {
808
809 private QuickContactActivity mQuickContactActivity;
810
811 public SelectAccountDialogFragmentListener() {}
812
813 @Override
814 public void onAccountChosen(AccountWithDataSet account, Bundle extraArgs) {
815 DirectoryContactUtil.createCopy(mQuickContactActivity.mContactData.getContentValues(),
816 account, mQuickContactActivity);
817 }
818
819 @Override
820 public void onAccountSelectorCancelled() {}
821
822 /**
823 * Set the parent activity. Since rotation can cause this fragment to be used across
824 * more than one activity instance, we need to explicitly set this value instead
825 * of making this class non-static.
826 */
827 public void setQuickContactActivity(QuickContactActivity quickContactActivity) {
828 mQuickContactActivity = quickContactActivity;
829 }
830 }
831
Brian Attwellb7e43642014-06-02 14:33:04 -0700832 final MultiShrinkScrollerListener mMultiShrinkScrollerListener
833 = new MultiShrinkScrollerListener() {
834 @Override
835 public void onScrolledOffBottom() {
Brian Attwellf1076dc2014-08-12 21:28:21 -0700836 finish();
Brian Attwellb7e43642014-06-02 14:33:04 -0700837 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700838
839 @Override
840 public void onEnterFullscreen() {
841 updateStatusBarColor();
842 }
843
844 @Override
845 public void onExitFullscreen() {
846 updateStatusBarColor();
847 }
Brian Attwell8477eaf2014-06-18 15:39:50 -0700848
849 @Override
850 public void onStartScrollOffBottom() {
Brian Attwell8477eaf2014-06-18 15:39:50 -0700851 mIsExitAnimationInProgress = true;
852 }
Brian Attwell8477eaf2014-06-18 15:39:50 -0700853
Brian Attwell8477eaf2014-06-18 15:39:50 -0700854 @Override
Brian Attwellf1076dc2014-08-12 21:28:21 -0700855 public void onEntranceAnimationDone() {
856 mIsEntranceAnimationFinished = true;
857 }
858
859 @Override
860 public void onTransparentViewHeightChange(float ratio) {
861 if (mIsEntranceAnimationFinished) {
862 mWindowScrim.setAlpha((int) (0xFF * ratio));
Brian Attwell8477eaf2014-06-18 15:39:50 -0700863 }
Brian Attwell8477eaf2014-06-18 15:39:50 -0700864 }
Brian Attwellb7e43642014-06-02 14:33:04 -0700865 };
866
Paul Souloseb64a4b2014-07-07 17:03:27 -0700867
868 /**
869 * Data items are compared to the same mimetype based off of three qualities:
870 * 1. Super primary
871 * 2. Primary
872 * 3. Times used
873 */
874 private final Comparator<DataItem> mWithinMimeTypeDataItemComparator =
875 new Comparator<DataItem>() {
876 @Override
877 public int compare(DataItem lhs, DataItem rhs) {
878 if (!lhs.getMimeType().equals(rhs.getMimeType())) {
879 Log.wtf(TAG, "Comparing DataItems with different mimetypes lhs.getMimeType(): " +
880 lhs.getMimeType() + " rhs.getMimeType(): " + rhs.getMimeType());
881 return 0;
882 }
883
884 if (lhs.isSuperPrimary()) {
885 return -1;
886 } else if (rhs.isSuperPrimary()) {
887 return 1;
888 } else if (lhs.isPrimary() && !rhs.isPrimary()) {
889 return -1;
890 } else if (!lhs.isPrimary() && rhs.isPrimary()) {
891 return 1;
892 } else {
893 final int lhsTimesUsed =
894 lhs.getTimesUsed() == null ? 0 : lhs.getTimesUsed();
895 final int rhsTimesUsed =
896 rhs.getTimesUsed() == null ? 0 : rhs.getTimesUsed();
897
898 return rhsTimesUsed - lhsTimesUsed;
899 }
900 }
901 };
902
Paul Souloscb4fcc72014-07-15 14:08:50 -0700903 /**
904 * Sorts among different mimetypes based off:
Brian Attwell05287bf2015-02-25 22:24:04 -0800905 * 1. Whether one of the mimetypes is the prioritized mimetype
906 * 2. Number of times used
907 * 3. Last time used
908 * 4. Statically defined
Paul Souloscb4fcc72014-07-15 14:08:50 -0700909 */
Paul Souloseb64a4b2014-07-07 17:03:27 -0700910 private final Comparator<List<DataItem>> mAmongstMimeTypeDataItemComparator =
911 new Comparator<List<DataItem>> () {
912 @Override
913 public int compare(List<DataItem> lhsList, List<DataItem> rhsList) {
Brian Attwell05287bf2015-02-25 22:24:04 -0800914 final DataItem lhs = lhsList.get(0);
915 final DataItem rhs = rhsList.get(0);
916 final String lhsMimeType = lhs.getMimeType();
917 final String rhsMimeType = rhs.getMimeType();
918
919 // 1. Whether one of the mimetypes is the prioritized mimetype
920 if (!TextUtils.isEmpty(mExtraPrioritizedMimeType) && !lhsMimeType.equals(rhsMimeType)) {
921 if (rhsMimeType.equals(mExtraPrioritizedMimeType)) {
922 return 1;
923 }
924 if (lhsMimeType.equals(mExtraPrioritizedMimeType)) {
925 return -1;
926 }
927 }
928
929 // 2. Number of times used
Paul Souloseb64a4b2014-07-07 17:03:27 -0700930 final int lhsTimesUsed = lhs.getTimesUsed() == null ? 0 : lhs.getTimesUsed();
931 final int rhsTimesUsed = rhs.getTimesUsed() == null ? 0 : rhs.getTimesUsed();
932 final int timesUsedDifference = rhsTimesUsed - lhsTimesUsed;
933 if (timesUsedDifference != 0) {
934 return timesUsedDifference;
935 }
936
Brian Attwell05287bf2015-02-25 22:24:04 -0800937 // 3. Last time used
Paul Souloseb64a4b2014-07-07 17:03:27 -0700938 final long lhsLastTimeUsed =
939 lhs.getLastTimeUsed() == null ? 0 : lhs.getLastTimeUsed();
940 final long rhsLastTimeUsed =
941 rhs.getLastTimeUsed() == null ? 0 : rhs.getLastTimeUsed();
942 final long lastTimeUsedDifference = rhsLastTimeUsed - lhsLastTimeUsed;
943 if (lastTimeUsedDifference > 0) {
944 return 1;
945 } else if (lastTimeUsedDifference < 0) {
946 return -1;
947 }
948
Brian Attwell05287bf2015-02-25 22:24:04 -0800949 // 4. Resort to a statically defined mimetype order.
950 if (!lhsMimeType.equals(rhsMimeType)) {
951 for (String mimeType : LEADING_MIMETYPES) {
952 if (lhsMimeType.equals(mimeType)) {
953 return -1;
954 } else if (rhsMimeType.equals(mimeType)) {
955 return 1;
956 }
Paul Souloseb64a4b2014-07-07 17:03:27 -0700957 }
958 }
Paul Souloseb64a4b2014-07-07 17:03:27 -0700959 return 0;
960 }
961 };
962
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700963 @Override
Nancy Chenf619e432014-08-18 20:15:12 -0700964 public boolean dispatchTouchEvent(MotionEvent ev) {
965 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
966 TouchPointManager.getInstance().setPoint((int) ev.getRawX(), (int) ev.getRawY());
967 }
968 return super.dispatchTouchEvent(ev);
969 }
970
971 @Override
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700972 protected void onCreate(Bundle savedInstanceState) {
973 Trace.beginSection("onCreate()");
974 super.onCreate(savedInstanceState);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700975
guanxiongliu04a19362016-05-18 14:38:51 -0700976 if (RequestPermissionsActivity.startPermissionActivity(this)) {
Brian Attwellbdd32642015-05-08 17:03:15 -0700977 return;
978 }
979
yaolu79525d02016-08-24 12:08:39 -0700980 mIsRecreatedInstance = (savedInstanceState != null);
981 mShouldLog = true;
982
guanxiongliu04a19362016-05-18 14:38:51 -0700983 // There're 3 states for each permission:
984 // 1. App doesn't have permission, not asked user yet.
985 // 2. App doesn't have permission, user denied it previously.
986 // 3. App has permission.
987 // Permission explanation card is displayed only for case 1.
Walter Jang5a16ef82016-07-27 09:20:59 -0700988 final boolean hasTelephonyFeature =
989 getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
990
guanxiongliu04a19362016-05-18 14:38:51 -0700991 final boolean hasCalendarPermission = PermissionsUtil.hasPermission(
Walter Jang0e723d92016-07-07 19:00:30 -0700992 this, Manifest.permission.READ_CALENDAR);
Walter Jang5a16ef82016-07-27 09:20:59 -0700993 final boolean hasSMSPermission = hasTelephonyFeature
994 && PermissionsUtil.hasPermission(this, Manifest.permission.READ_SMS);
guanxiongliu04a19362016-05-18 14:38:51 -0700995
996 final boolean wasCalendarPermissionDenied =
997 ActivityCompat.shouldShowRequestPermissionRationale(
Walter Jang0e723d92016-07-07 19:00:30 -0700998 this, Manifest.permission.READ_CALENDAR);
guanxiongliu04a19362016-05-18 14:38:51 -0700999 final boolean wasSMSPermissionDenied =
Walter Jang5a16ef82016-07-27 09:20:59 -07001000 hasTelephonyFeature && ActivityCompat.shouldShowRequestPermissionRationale(
Walter Jang0e723d92016-07-07 19:00:30 -07001001 this, Manifest.permission.READ_SMS);
guanxiongliu04a19362016-05-18 14:38:51 -07001002
1003 final boolean shouldDisplayCalendarMessage =
1004 !hasCalendarPermission && !wasCalendarPermissionDenied;
Walter Jang5a16ef82016-07-27 09:20:59 -07001005 final boolean shouldDisplaySMSMessage =
1006 hasTelephonyFeature && !hasSMSPermission && !wasSMSPermissionDenied;
guanxiongliu04a19362016-05-18 14:38:51 -07001007 mShouldShowPermissionExplanation = shouldDisplayCalendarMessage || shouldDisplaySMSMessage;
1008
1009 if (shouldDisplayCalendarMessage && shouldDisplaySMSMessage) {
1010 mPermissionExplanationCardSubHeader =
1011 getString(R.string.permission_explanation_subheader_calendar_and_SMS);
1012 } else if (shouldDisplayCalendarMessage) {
1013 mPermissionExplanationCardSubHeader =
1014 getString(R.string.permission_explanation_subheader_calendar);
1015 } else if (shouldDisplaySMSMessage) {
1016 mPermissionExplanationCardSubHeader =
1017 getString(R.string.permission_explanation_subheader_SMS);
1018 }
1019
Walter Jang1bb6c292016-02-29 11:46:03 -08001020 final int previousScreenType = getIntent().getIntExtra
1021 (EXTRA_PREVIOUS_SCREEN_TYPE, ScreenType.UNKNOWN);
Walter Jang62a20742016-02-24 17:04:12 -08001022 Logger.logScreenView(this, ScreenType.QUICK_CONTACT, previousScreenType);
1023
yaolu79525d02016-08-24 12:08:39 -07001024 mReferrer = getCallingPackage();
1025 if (mReferrer == null && CompatUtils.isLollipopMr1Compatible() && getReferrer() != null) {
1026 mReferrer = getReferrer().getAuthority();
1027 }
1028 mContactType = ContactType.UNKNOWN_TYPE;
1029
Wenyi Wang4c3d3e22015-12-17 14:30:02 -08001030 if (CompatUtils.isLollipopCompatible()) {
1031 getWindow().setStatusBarColor(Color.TRANSPARENT);
1032 }
Daniel Lehmann2426cb02012-05-10 18:41:21 -07001033
Brian Attwelld28851f2014-06-10 13:25:07 -07001034 processIntent(getIntent());
Makoto Onukidfe8cc82012-05-17 18:03:44 -07001035
Daisuke Miyakawa10d7df72011-07-29 16:11:05 -07001036 // Show QuickContact in front of soft input
1037 getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
1038 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
1039
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001040 setContentView(R.layout.quickcontact_activity);
1041
Brian Attwella3859ed2014-07-15 13:51:55 -07001042 mMaterialColorMapUtils = new MaterialColorMapUtils(getResources());
1043
Paul Soulos0cda9ae2014-07-23 11:27:28 -07001044 mScroller = (MultiShrinkScroller) findViewById(R.id.multiscroller);
1045
Paul Souloseb64a4b2014-07-07 17:03:27 -07001046 mContactCard = (ExpandingEntryCardView) findViewById(R.id.communication_card);
Brian Attwell60953692014-07-11 17:18:46 -07001047 mNoContactDetailsCard = (ExpandingEntryCardView) findViewById(R.id.no_contact_data_card);
Paul Soulosb3054e52014-06-05 16:46:02 -07001048 mRecentCard = (ExpandingEntryCardView) findViewById(R.id.recent_card);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001049 mAboutCard = (ExpandingEntryCardView) findViewById(R.id.about_card);
guanxiongliu04a19362016-05-18 14:38:51 -07001050 mPermissionExplanationCard =
1051 (ExpandingEntryCardView) findViewById(R.id.permission_explanation_card);
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001052
Gary Maid1d8a7a2016-06-07 14:46:58 -07001053 if (mShouldShowSuggestions) {
1054 mCollapsedSuggestionCardView = (CardView) findViewById(R.id.collapsed_suggestion_card);
1055 mExpandSuggestionCardView = (CardView) findViewById(R.id.expand_suggestion_card);
1056 mCollapsedSuggestionHeader = findViewById(R.id.collapsed_suggestion_header);
1057 mCollapsedSuggestionCardTitle = (TextView) findViewById(
1058 R.id.collapsed_suggestion_card_title);
1059 mExpandSuggestionCardTitle = (TextView) findViewById(R.id.expand_suggestion_card_title);
1060 mSuggestionSummaryPhoto = (ImageView) findViewById(R.id.suggestion_icon);
1061 mSuggestionForName = (TextView) findViewById(R.id.suggestion_for_name);
1062 mSuggestionContactsNumber = (TextView) findViewById(
1063 R.id.suggestion_for_contacts_number);
1064 mSuggestionList = (LinearLayout) findViewById(R.id.suggestion_list);
1065 mSuggestionsCancelButton = (Button) findViewById(R.id.cancel_button);
1066 mSuggestionsLinkButton = (Button) findViewById(R.id.link_button);
1067 if (savedInstanceState != null) {
1068 mIsSuggestionListCollapsed = savedInstanceState.getBoolean(
1069 KEY_IS_SUGGESTION_LIST_COLLAPSED, true);
1070 mPreviousContactId = savedInstanceState.getLong(KEY_PREVIOUS_CONTACT_ID);
1071 mSuggestionsShouldAutoSelected = savedInstanceState.getBoolean(
1072 KEY_SUGGESTIONS_AUTO_SELECTED, true);
1073 mSelectedAggregationIds = (TreeSet<Long>)
1074 savedInstanceState.getSerializable(KEY_SELECTED_SUGGESTION_CONTACTS);
1075 } else {
Tingting Wang18906c02015-10-16 14:53:10 -07001076 mIsSuggestionListCollapsed = true;
Gary Maid1d8a7a2016-06-07 14:46:58 -07001077 mSelectedAggregationIds.clear();
Tingting Wang18906c02015-10-16 14:53:10 -07001078 }
Gary Maid1d8a7a2016-06-07 14:46:58 -07001079 if (mSelectedAggregationIds.isEmpty()) {
1080 disableLinkButton();
1081 } else {
1082 enableLinkButton();
1083 }
1084 mCollapsedSuggestionHeader.setOnClickListener(new OnClickListener() {
1085 @Override
1086 public void onClick(View view) {
1087 mCollapsedSuggestionCardView.setVisibility(View.GONE);
1088 mExpandSuggestionCardView.setVisibility(View.VISIBLE);
1089 mIsSuggestionListCollapsed = false;
1090 mExpandSuggestionCardTitle.requestFocus();
1091 mExpandSuggestionCardTitle.sendAccessibilityEvent(
1092 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
1093 }
1094 });
1095
1096 mSuggestionsCancelButton.setOnClickListener(new OnClickListener() {
1097 @Override
1098 public void onClick(View view) {
1099 mCollapsedSuggestionCardView.setVisibility(View.VISIBLE);
1100 mExpandSuggestionCardView.setVisibility(View.GONE);
1101 mIsSuggestionListCollapsed = true;
1102 }
1103 });
1104 }
Tingting Wang18906c02015-10-16 14:53:10 -07001105
guanxiongliu04a19362016-05-18 14:38:51 -07001106 mPermissionExplanationCard.setOnClickListener(mEntryClickHandler);
Brian Attwell60953692014-07-11 17:18:46 -07001107 mNoContactDetailsCard.setOnClickListener(mEntryClickHandler);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001108 mContactCard.setOnClickListener(mEntryClickHandler);
Paul Soulos2a4207f2014-07-31 17:09:05 -07001109 mContactCard.setOnCreateContextMenuListener(mEntryContextMenuListener);
Paul Soulosb3054e52014-06-05 16:46:02 -07001110
1111 mRecentCard.setOnClickListener(mEntryClickHandler);
1112 mRecentCard.setTitle(getResources().getString(R.string.recent_card_title));
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001113
Paul Souloseb64a4b2014-07-07 17:03:27 -07001114 mAboutCard.setOnClickListener(mEntryClickHandler);
Paul Soulos2a4207f2014-07-31 17:09:05 -07001115 mAboutCard.setOnCreateContextMenuListener(mEntryContextMenuListener);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001116
Brian Attwell02eaf112014-08-27 21:03:42 -07001117 mPhotoView = (QuickContactImageView) findViewById(R.id.photo);
Brian Attwell63176c92014-08-18 15:14:18 -07001118 final View transparentView = findViewById(R.id.transparent_view);
Brian Attwell0d90afe2014-06-18 16:45:41 -07001119 if (mScroller != null) {
Brian Attwell63176c92014-08-18 15:14:18 -07001120 transparentView.setOnClickListener(new OnClickListener() {
Brian Attwell0d90afe2014-06-18 16:45:41 -07001121 @Override
1122 public void onClick(View v) {
1123 mScroller.scrollOffBottom();
1124 }
1125 });
1126 }
Yorke Lee62196682013-09-06 18:34:17 -07001127
Brian Attwelld41ab8a2014-08-07 11:08:55 -07001128 // Allow a shadow to be shown under the toolbar.
1129 ViewUtil.addRectangularOutlineProvider(findViewById(R.id.toolbar_parent), getResources());
1130
Brian Attwelld28851f2014-06-10 13:25:07 -07001131 final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
1132 setActionBar(toolbar);
Brian Attwell9b889e62014-06-23 18:25:32 -07001133 getActionBar().setTitle(null);
1134 // Put a TextView with a known resource id into the ActionBar. This allows us to easily
1135 // find the correct TextView location & size later.
1136 toolbar.addView(getLayoutInflater().inflate(R.layout.quickcontact_title_placeholder, null));
Yorke Lee62196682013-09-06 18:34:17 -07001137
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001138 mHasAlreadyBeenOpened = savedInstanceState != null;
Brian Attwellf1076dc2014-08-12 21:28:21 -07001139 mIsEntranceAnimationFinished = mHasAlreadyBeenOpened;
Brian Attwell9b889e62014-06-23 18:25:32 -07001140 mWindowScrim = new ColorDrawable(SCRIM_COLOR);
Brian Attwellf1076dc2014-08-12 21:28:21 -07001141 mWindowScrim.setAlpha(0);
Brian Attwell9b889e62014-06-23 18:25:32 -07001142 getWindow().setBackgroundDrawable(mWindowScrim);
Makoto Onukidfe8cc82012-05-17 18:03:44 -07001143
Wenyi Wangcaf26192016-05-09 15:00:25 -07001144 mScroller.initialize(mMultiShrinkScrollerListener, mExtraMode == MODE_FULLY_EXPANDED,
1145 /* maximumHeaderTextSize */ -1,
1146 /* shouldUpdateNameViewHeight */ true);
Brian Attwellac3ed8e2014-06-27 17:24:42 -07001147 // mScroller needs to perform asynchronous measurements after initalize(), therefore
1148 // we can't mark this as GONE.
1149 mScroller.setVisibility(View.INVISIBLE);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001150
Brian Attwell9b889e62014-06-23 18:25:32 -07001151 setHeaderNameText(R.string.missing_name);
1152
Brian Attwelld28851f2014-06-10 13:25:07 -07001153 mSelectAccountFragmentListener= (SelectAccountDialogFragmentListener) getFragmentManager()
1154 .findFragmentByTag(FRAGMENT_TAG_SELECT_ACCOUNT);
1155 if (mSelectAccountFragmentListener == null) {
1156 mSelectAccountFragmentListener = new SelectAccountDialogFragmentListener();
1157 getFragmentManager().beginTransaction().add(0, mSelectAccountFragmentListener,
1158 FRAGMENT_TAG_SELECT_ACCOUNT).commit();
1159 mSelectAccountFragmentListener.setRetainInstance(true);
1160 }
1161 mSelectAccountFragmentListener.setQuickContactActivity(this);
Paul Soulos23889ba2014-06-10 14:55:32 -07001162
Brian Attwellf1076dc2014-08-12 21:28:21 -07001163 SchedulingUtils.doOnPreDraw(mScroller, /* drawNextFrame = */ true,
1164 new Runnable() {
1165 @Override
1166 public void run() {
1167 if (!mHasAlreadyBeenOpened) {
1168 // The initial scrim opacity must match the scrim opacity that would be
1169 // achieved by scrolling to the starting position.
1170 final float alphaRatio = mExtraMode == MODE_FULLY_EXPANDED ?
1171 1 : mScroller.getStartingTransparentHeightRatio();
1172 final int duration = getResources().getInteger(
1173 android.R.integer.config_shortAnimTime);
1174 final int desiredAlpha = (int) (0xFF * alphaRatio);
1175 ObjectAnimator o = ObjectAnimator.ofInt(mWindowScrim, "alpha", 0,
1176 desiredAlpha).setDuration(duration);
1177
1178 o.start();
1179 }
1180 }
1181 });
1182
Brian Attwell9b889e62014-06-23 18:25:32 -07001183 if (savedInstanceState != null) {
1184 final int color = savedInstanceState.getInt(KEY_THEME_COLOR, 0);
Brian Attwellac3ed8e2014-06-27 17:24:42 -07001185 SchedulingUtils.doOnPreDraw(mScroller, /* drawNextFrame = */ false,
1186 new Runnable() {
1187 @Override
1188 public void run() {
1189 // Need to wait for the pre draw before setting the initial scroll
1190 // value. Prior to pre draw all scroll values are invalid.
1191 if (mHasAlreadyBeenOpened) {
1192 mScroller.setVisibility(View.VISIBLE);
1193 mScroller.setScroll(mScroller.getScrollNeededToBeFullScreen());
1194 }
1195 // Need to wait for pre draw for setting the theme color. Setting the
1196 // header tint before the MultiShrinkScroller has been measured will
1197 // cause incorrect tinting calculations.
1198 if (color != 0) {
Brian Attwella3859ed2014-07-15 13:51:55 -07001199 setThemeColor(mMaterialColorMapUtils
1200 .calculatePrimaryAndSecondaryColor(color));
Brian Attwell9b889e62014-06-23 18:25:32 -07001201 }
Brian Attwellac3ed8e2014-06-27 17:24:42 -07001202 }
1203 });
Brian Attwell9b889e62014-06-23 18:25:32 -07001204 }
1205
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001206 Trace.endSection();
1207 }
1208
Paul Soulos405ae402014-07-14 16:16:36 -07001209 @Override
1210 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Wenyi Wangd1cb9042015-11-05 17:17:59 -08001211 final boolean deletedOrSplit = requestCode == REQUEST_CODE_CONTACT_EDITOR_ACTIVITY &&
1212 (resultCode == ContactDeletionInteraction.RESULT_CODE_DELETED ||
1213 resultCode == ContactEditorBaseActivity.RESULT_CODE_SPLIT);
Gary Mai0d76abc2016-05-25 16:19:45 -07001214 setResult(resultCode, data);
Wenyi Wangd1cb9042015-11-05 17:17:59 -08001215 if (deletedOrSplit) {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001216 finish();
Paul Soulosab54ea12014-08-28 17:22:38 -07001217 } else if (requestCode == REQUEST_CODE_CONTACT_SELECTION_ACTIVITY &&
1218 resultCode != RESULT_CANCELED) {
1219 processIntent(data);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001220 }
1221 }
1222
1223 @Override
1224 protected void onNewIntent(Intent intent) {
1225 super.onNewIntent(intent);
1226 mHasAlreadyBeenOpened = true;
Brian Attwellf1076dc2014-08-12 21:28:21 -07001227 mIsEntranceAnimationFinished = true;
Brian Attwell8571dd32014-06-23 23:29:10 -07001228 mHasComputedThemeColor = false;
Brian Attwelld28851f2014-06-10 13:25:07 -07001229 processIntent(intent);
1230 }
1231
Brian Attwell9b889e62014-06-23 18:25:32 -07001232 @Override
1233 public void onSaveInstanceState(Bundle savedInstanceState) {
1234 super.onSaveInstanceState(savedInstanceState);
1235 if (mColorFilter != null) {
Brian Attwell8e29faf2015-01-21 10:22:40 -08001236 savedInstanceState.putInt(KEY_THEME_COLOR, mColorFilterColor);
Brian Attwell9b889e62014-06-23 18:25:32 -07001237 }
Tingting Wange3a7c4f2015-09-24 18:23:18 -07001238 savedInstanceState.putBoolean(KEY_IS_SUGGESTION_LIST_COLLAPSED, mIsSuggestionListCollapsed);
Tingting Wang18906c02015-10-16 14:53:10 -07001239 savedInstanceState.putLong(KEY_PREVIOUS_CONTACT_ID, mPreviousContactId);
1240 savedInstanceState.putBoolean(
1241 KEY_SUGGESTIONS_AUTO_SELECTED, mSuggestionsShouldAutoSelected);
Tingting Wange3a7c4f2015-09-24 18:23:18 -07001242 savedInstanceState.putSerializable(
1243 KEY_SELECTED_SUGGESTION_CONTACTS, mSelectedAggregationIds);
Brian Attwell9b889e62014-06-23 18:25:32 -07001244 }
1245
Brian Attwelld28851f2014-06-10 13:25:07 -07001246 private void processIntent(Intent intent) {
Paul Soulosf19dda92014-09-15 15:48:02 -07001247 if (intent == null) {
1248 finish();
1249 return;
1250 }
Brian Attwelld28851f2014-06-10 13:25:07 -07001251 Uri lookupUri = intent.getData();
Gary Mai250fc162016-06-10 16:54:15 -07001252 if (intent.getBooleanExtra(EXTRA_CONTACT_EDITED, false)) {
1253 setResult(ContactEditorBaseActivity.RESULT_CODE_EDITED);
1254 }
Brian Attwelld28851f2014-06-10 13:25:07 -07001255
1256 // Check to see whether it comes from the old version.
1257 if (lookupUri != null && LEGACY_AUTHORITY.equals(lookupUri.getAuthority())) {
1258 final long rawContactId = ContentUris.parseId(lookupUri);
1259 lookupUri = RawContacts.getContactLookupUri(getContentResolver(),
1260 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
1261 }
Brian Attwell05287bf2015-02-25 22:24:04 -08001262 mExtraMode = getIntent().getIntExtra(QuickContact.EXTRA_MODE, QuickContact.MODE_LARGE);
Wenyi Wangcaf26192016-05-09 15:00:25 -07001263 if (isMultiWindowOnPhone()) {
1264 mExtraMode = QuickContact.MODE_LARGE;
1265 }
1266 mExtraPrioritizedMimeType =
1267 getIntent().getStringExtra(QuickContact.EXTRA_PRIORITIZED_MIMETYPE);
Brian Attwelld28851f2014-06-10 13:25:07 -07001268 final Uri oldLookupUri = mLookupUri;
1269
Jay Shraunerfcfcae32014-11-24 10:23:36 -08001270 if (lookupUri == null) {
1271 finish();
1272 return;
1273 }
1274 mLookupUri = lookupUri;
Brian Attwelld28851f2014-06-10 13:25:07 -07001275 mExcludeMimes = intent.getStringArrayExtra(QuickContact.EXTRA_EXCLUDE_MIMES);
1276 if (oldLookupUri == null) {
yaolu79525d02016-08-24 12:08:39 -07001277 // Should not log if only orientation changes.
1278 mShouldLog = !mIsRecreatedInstance;
Brian Attwelld28851f2014-06-10 13:25:07 -07001279 mContactLoader = (ContactLoader) getLoaderManager().initLoader(
1280 LOADER_CONTACT_ID, null, mLoaderContactCallbacks);
1281 } else if (oldLookupUri != mLookupUri) {
yaolu79525d02016-08-24 12:08:39 -07001282 // Should log when reload happens, regardless of orientation change.
1283 mShouldLog = true;
Brian Attwelld28851f2014-06-10 13:25:07 -07001284 // After copying a directory contact, the contact URI changes. Therefore,
Wenyi Wang144a4d12016-02-17 11:46:23 -08001285 // we need to reload the new contact.
Brian Attwell59a585b2014-10-17 18:07:10 -07001286 destroyInteractionLoaders();
Wenyi Wang144a4d12016-02-17 11:46:23 -08001287 mContactLoader = (ContactLoader) (Loader<?>) getLoaderManager().getLoader(
1288 LOADER_CONTACT_ID);
Walter Jang27a2fac2016-03-14 10:44:14 -07001289 mContactLoader.setLookupUri(mLookupUri);
Brian Attwell59a585b2014-10-17 18:07:10 -07001290 mCachedCp2DataCardModel = null;
Brian Attwelld28851f2014-06-10 13:25:07 -07001291 }
Wenyi Wang144a4d12016-02-17 11:46:23 -08001292 mContactLoader.forceLoad();
Brian Attwell2d150da2014-07-09 22:35:56 -07001293
1294 NfcHandler.register(this, mLookupUri);
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001295 }
1296
Brian Attwell59a585b2014-10-17 18:07:10 -07001297 private void destroyInteractionLoaders() {
1298 for (int interactionLoaderId : mRecentLoaderIds) {
1299 getLoaderManager().destroyLoader(interactionLoaderId);
1300 }
1301 }
1302
Brian Attwellb7e43642014-06-02 14:33:04 -07001303 private void runEntranceAnimation() {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001304 if (mHasAlreadyBeenOpened) {
1305 return;
1306 }
1307 mHasAlreadyBeenOpened = true;
Wenyi Wangcaf26192016-05-09 15:00:25 -07001308 mScroller.scrollUpForEntranceAnimation(/* scrollToCurrentPosition */ !isMultiWindowOnPhone()
1309 && (mExtraMode != MODE_FULLY_EXPANDED));
1310 }
1311
1312 private boolean isMultiWindowOnPhone() {
1313 return MultiWindowCompat.isInMultiWindowMode(this) && PhoneCapabilityTester.isPhone(this);
Brian Attwellb7e43642014-06-02 14:33:04 -07001314 }
1315
Katherine Kuan81281ee2011-07-28 16:20:59 -07001316 /** Assign this string to the view if it is not empty. */
Brian Attwelld28851f2014-06-10 13:25:07 -07001317 private void setHeaderNameText(int resId) {
Brian Attwell9b889e62014-06-23 18:25:32 -07001318 if (mScroller != null) {
Tingting Wang9f153a02015-11-05 18:11:04 -08001319 mScroller.setTitle(getText(resId) == null ? null : getText(resId).toString(),
1320 /* isPhoneNumber= */ false);
Brian Attwell9b889e62014-06-23 18:25:32 -07001321 }
Katherine Kuan81281ee2011-07-28 16:20:59 -07001322 }
1323
1324 /** Assign this string to the view if it is not empty. */
Tingting Wang9f153a02015-11-05 18:11:04 -08001325 private void setHeaderNameText(String value, boolean isPhoneNumber) {
Brian Attwelld28851f2014-06-10 13:25:07 -07001326 if (!TextUtils.isEmpty(value)) {
Brian Attwell9b889e62014-06-23 18:25:32 -07001327 if (mScroller != null) {
Tingting Wang9f153a02015-11-05 18:11:04 -08001328 mScroller.setTitle(value, isPhoneNumber);
Brian Attwell9b889e62014-06-23 18:25:32 -07001329 }
Katherine Kuan81281ee2011-07-28 16:20:59 -07001330 }
1331 }
1332
1333 /**
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001334 * Check if the given MIME-type appears in the list of excluded MIME-types
1335 * that the most-recent caller requested.
1336 */
1337 private boolean isMimeExcluded(String mimeType) {
1338 if (mExcludeMimes == null) return false;
1339 for (String excludedMime : mExcludeMimes) {
1340 if (TextUtils.equals(excludedMime, mimeType)) {
1341 return true;
1342 }
1343 }
1344 return false;
1345 }
1346
1347 /**
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02001348 * Handle the result from the ContactLoader
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001349 */
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001350 private void bindContactData(final Contact data) {
1351 Trace.beginSection("bindContactData");
yaolu79525d02016-08-24 12:08:39 -07001352
1353 final int actionType = mContactData == null ? ActionType.START : ActionType.UNKNOWN_ACTION;
Yorke Lee6cb6f6f2013-09-26 16:26:03 -07001354 mContactData = data;
yaolu79525d02016-08-24 12:08:39 -07001355
1356 final int newContactType;
1357 if (DirectoryContactUtil.isDirectoryContact(mContactData)) {
1358 newContactType = ContactType.DIRECTORY;
1359 } else if (InvisibleContactUtil.isInvisibleAndAddable(mContactData, this)) {
1360 newContactType = ContactType.INVISIBLE_AND_ADDABLE;
1361 } else if (isContactEditable()) {
1362 newContactType = ContactType.EDITABLE;
1363 } else {
1364 newContactType = ContactType.UNKNOWN_TYPE;
1365 }
1366 if (mShouldLog && mContactType != newContactType) {
1367 Logger.logQuickContactEvent( mReferrer, newContactType, CardType.UNKNOWN_CARD,
1368 actionType, /* thirdPartyAction */ null);
1369 }
1370 mContactType = newContactType;
1371
Brian Attwelld28851f2014-06-10 13:25:07 -07001372 invalidateOptionsMenu();
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001373
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001374 Trace.endSection();
1375 Trace.beginSection("Set display photo & name");
Makoto Onukidfe8cc82012-05-17 18:03:44 -07001376
Brian Attwell02eaf112014-08-27 21:03:42 -07001377 mPhotoView.setIsBusiness(mContactData.isDisplayNameFromOrganization());
Yorke Lee62196682013-09-06 18:34:17 -07001378 mPhotoSetter.setupContactPhoto(data, mPhotoView);
Brian Attwell31b2d422014-06-05 00:14:58 -07001379 extractAndApplyTintFromPhotoViewAsynchronously();
Tingting Wangdf65d162015-07-24 17:04:35 -07001380 final String displayName = ContactDisplayUtils.getDisplayName(this, data).toString();
Tingting Wang9f153a02015-11-05 18:11:04 -08001381 setHeaderNameText(
1382 displayName, mContactData.getDisplayNameSource() == DisplayNameSources.PHONE);
Tingting Wangdf65d162015-07-24 17:04:35 -07001383 final String phoneticName = ContactDisplayUtils.getPhoneticName(this, data);
1384 if (mScroller != null) {
Tingting Wang469a70f2016-03-08 14:50:29 -08001385 // Show phonetic name only when it doesn't equal the display name.
1386 if (!TextUtils.isEmpty(phoneticName) && !phoneticName.equals(displayName)) {
Tingting Wangdf65d162015-07-24 17:04:35 -07001387 mScroller.setPhoneticName(phoneticName);
1388 } else {
1389 mScroller.setPhoneticNameGone();
1390 }
Tingting Wangd5b686e2015-07-13 12:52:40 -07001391 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001392
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001393 Trace.endSection();
Makoto Onukidfe8cc82012-05-17 18:03:44 -07001394
Brian Attwell6bb01342014-08-20 23:16:00 -07001395 mEntriesAndActionsTask = new AsyncTask<Void, Void, Cp2DataCardModel>() {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001396
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001397 @Override
Brian Attwell6bb01342014-08-20 23:16:00 -07001398 protected Cp2DataCardModel doInBackground(
Paul Souloseb64a4b2014-07-07 17:03:27 -07001399 Void... params) {
1400 return generateDataModelFromContact(data);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001401 }
1402
1403 @Override
Brian Attwell6bb01342014-08-20 23:16:00 -07001404 protected void onPostExecute(Cp2DataCardModel cardDataModel) {
1405 super.onPostExecute(cardDataModel);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001406 // Check that original AsyncTask parameters are still valid and the activity
1407 // is still running before binding to UI. A new intent could invalidate
1408 // the results, for example.
1409 if (data == mContactData && !isCancelled()) {
Brian Attwell6bb01342014-08-20 23:16:00 -07001410 bindDataToCards(cardDataModel);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001411 showActivity();
1412 }
1413 }
1414 };
1415 mEntriesAndActionsTask.execute();
1416 }
1417
Brian Attwell6bb01342014-08-20 23:16:00 -07001418 private void bindDataToCards(Cp2DataCardModel cp2DataCardModel) {
1419 startInteractionLoaders(cp2DataCardModel);
Wenyi Wanga26a57a2016-03-02 17:53:23 -08001420 populateContactAndAboutCard(cp2DataCardModel, /* shouldAddPhoneticName */ true);
Tingting Wange3a7c4f2015-09-24 18:23:18 -07001421 populateSuggestionCard();
Paul Souloseb64a4b2014-07-07 17:03:27 -07001422 }
1423
Brian Attwell6bb01342014-08-20 23:16:00 -07001424 private void startInteractionLoaders(Cp2DataCardModel cp2DataCardModel) {
1425 final Map<String, List<DataItem>> dataItemsMap = cp2DataCardModel.dataItemsMap;
1426 final List<DataItem> phoneDataItems = dataItemsMap.get(Phone.CONTENT_ITEM_TYPE);
guanxiongliuc7a4b9c2016-04-30 20:19:21 -07001427 final List<DataItem> sipCallDataItems = dataItemsMap.get(SipAddress.CONTENT_ITEM_TYPE);
Paul Soulos97f27802014-09-08 13:55:45 -07001428 if (phoneDataItems != null && phoneDataItems.size() == 1) {
1429 mOnlyOnePhoneNumber = true;
1430 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001431 String[] phoneNumbers = null;
1432 if (phoneDataItems != null) {
1433 phoneNumbers = new String[phoneDataItems.size()];
1434 for (int i = 0; i < phoneDataItems.size(); ++i) {
1435 phoneNumbers[i] = ((PhoneDataItem) phoneDataItems.get(i)).getNumber();
1436 }
1437 }
guanxiongliuc7a4b9c2016-04-30 20:19:21 -07001438 String[] sipNumbers = null;
1439 if (sipCallDataItems != null) {
1440 sipNumbers = new String[sipCallDataItems.size()];
1441 for (int i = 0; i < sipCallDataItems.size(); ++i) {
1442 sipNumbers[i] = ((SipAddressDataItem) sipCallDataItems.get(i)).getSipAddress();
1443 }
1444 }
Paul Soulosab840442014-06-17 14:08:40 -07001445 final Bundle phonesExtraBundle = new Bundle();
Paul Souloseb64a4b2014-07-07 17:03:27 -07001446 phonesExtraBundle.putStringArray(KEY_LOADER_EXTRA_PHONES, phoneNumbers);
guanxiongliuc7a4b9c2016-04-30 20:19:21 -07001447 phonesExtraBundle.putStringArray(KEY_LOADER_EXTRA_SIP_NUMBERS, sipNumbers);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001448
1449 Trace.beginSection("start sms loader");
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001450 getLoaderManager().initLoader(
1451 LOADER_SMS_ID,
Paul Soulosab840442014-06-17 14:08:40 -07001452 phonesExtraBundle,
1453 mLoaderInteractionsCallbacks);
1454 Trace.endSection();
1455
1456 Trace.beginSection("start call log loader");
1457 getLoaderManager().initLoader(
1458 LOADER_CALL_LOG_ID,
1459 phonesExtraBundle,
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001460 mLoaderInteractionsCallbacks);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001461 Trace.endSection();
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001462
Paul Souloseb64a4b2014-07-07 17:03:27 -07001463
Paul Soulos899aa212014-06-11 12:04:43 -07001464 Trace.beginSection("start calendar loader");
Brian Attwell6bb01342014-08-20 23:16:00 -07001465 final List<DataItem> emailDataItems = dataItemsMap.get(Email.CONTENT_ITEM_TYPE);
Paul Soulos97f27802014-09-08 13:55:45 -07001466 if (emailDataItems != null && emailDataItems.size() == 1) {
1467 mOnlyOneEmail = true;
1468 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001469 String[] emailAddresses = null;
1470 if (emailDataItems != null) {
1471 emailAddresses = new String[emailDataItems.size()];
1472 for (int i = 0; i < emailDataItems.size(); ++i) {
1473 emailAddresses[i] = ((EmailDataItem) emailDataItems.get(i)).getAddress();
1474 }
1475 }
Paul Soulosab840442014-06-17 14:08:40 -07001476 final Bundle emailsExtraBundle = new Bundle();
Paul Souloseb64a4b2014-07-07 17:03:27 -07001477 emailsExtraBundle.putStringArray(KEY_LOADER_EXTRA_EMAILS, emailAddresses);
Paul Soulos899aa212014-06-11 12:04:43 -07001478 getLoaderManager().initLoader(
1479 LOADER_CALENDAR_ID,
Paul Soulosab840442014-06-17 14:08:40 -07001480 emailsExtraBundle,
Paul Soulos899aa212014-06-11 12:04:43 -07001481 mLoaderInteractionsCallbacks);
1482 Trace.endSection();
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001483 }
1484
1485 private void showActivity() {
1486 if (mScroller != null) {
1487 mScroller.setVisibility(View.VISIBLE);
1488 SchedulingUtils.doOnPreDraw(mScroller, /* drawNextFrame = */ false,
1489 new Runnable() {
1490 @Override
1491 public void run() {
1492 runEntranceAnimation();
1493 }
1494 });
1495 }
1496 }
1497
Brian Attwell6bb01342014-08-20 23:16:00 -07001498 private List<List<Entry>> buildAboutCardEntries(Map<String, List<DataItem>> dataItemsMap) {
Paul Soulosa153dba2014-08-06 13:44:57 -07001499 final List<List<Entry>> aboutCardEntries = new ArrayList<>();
1500 for (String mimetype : SORTED_ABOUT_CARD_MIMETYPES) {
Brian Attwell6bb01342014-08-20 23:16:00 -07001501 final List<DataItem> mimeTypeItems = dataItemsMap.get(mimetype);
Paul Soulosa153dba2014-08-06 13:44:57 -07001502 if (mimeTypeItems == null) {
1503 continue;
1504 }
Brian Attwell6bb01342014-08-20 23:16:00 -07001505 // Set aboutCardTitleOut = null, since SORTED_ABOUT_CARD_MIMETYPES doesn't contain
1506 // the name mimetype.
1507 final List<Entry> aboutEntries = dataItemsToEntries(mimeTypeItems,
1508 /* aboutCardTitleOut = */ null);
Paul Soulosa153dba2014-08-06 13:44:57 -07001509 if (aboutEntries.size() > 0) {
1510 aboutCardEntries.add(aboutEntries);
1511 }
1512 }
1513 return aboutCardEntries;
1514 }
1515
Paul Soulosa42ef762014-08-20 10:26:10 -07001516 @Override
1517 protected void onResume() {
1518 super.onResume();
1519 // If returning from a launched activity, repopulate the contact and about card
1520 if (mHasIntentLaunched) {
1521 mHasIntentLaunched = false;
Wenyi Wanga26a57a2016-03-02 17:53:23 -08001522 populateContactAndAboutCard(mCachedCp2DataCardModel, /* shouldAddPhoneticName */ false);
Paul Soulosa42ef762014-08-20 10:26:10 -07001523 }
Brian Attwell59a585b2014-10-17 18:07:10 -07001524
1525 // When exiting the activity and resuming, we want to force a full reload of all the
1526 // interaction data in case something changed in the background. On screen rotation,
1527 // we don't need to do this. And, mCachedCp2DataCardModel will be null, so we won't.
1528 if (mCachedCp2DataCardModel != null) {
1529 destroyInteractionLoaders();
1530 startInteractionLoaders(mCachedCp2DataCardModel);
1531 }
Paul Soulosa42ef762014-08-20 10:26:10 -07001532 }
1533
Tingting Wange3a7c4f2015-09-24 18:23:18 -07001534 private void populateSuggestionCard() {
Gary Maid1d8a7a2016-06-07 14:46:58 -07001535 if (!mShouldShowSuggestions) {
1536 return;
1537 }
Tingting Wange3a7c4f2015-09-24 18:23:18 -07001538 // Initialize suggestion related view and data.
Tingting Wang18906c02015-10-16 14:53:10 -07001539 if (mPreviousContactId != mContactData.getId()) {
1540 mCollapsedSuggestionCardView.setVisibility(View.GONE);
1541 mExpandSuggestionCardView.setVisibility(View.GONE);
1542 mIsSuggestionListCollapsed = true;
Tingting Wang42a08d92015-11-20 11:26:21 -08001543 mSuggestionsShouldAutoSelected = true;
Tingting Wang0fafb0e2015-10-14 12:18:45 -07001544 mSuggestionList.removeAllViews();
1545 }
Tingting Wange3a7c4f2015-09-24 18:23:18 -07001546
Tingting Wang3d65ea52015-11-13 09:36:02 -08001547 // Do not show the card when it's directory contact or invisible.
1548 if (DirectoryContactUtil.isDirectoryContact(mContactData)
1549 || InvisibleContactUtil.isInvisibleAndAddable(mContactData, this)) {
1550 return;
1551 }
1552
Tingting Wange3a7c4f2015-09-24 18:23:18 -07001553 if (mAggregationSuggestionEngine == null) {
1554 mAggregationSuggestionEngine = new AggregationSuggestionEngine(this);
1555 mAggregationSuggestionEngine.setListener(this);
Tingting Wange29b6942015-09-30 15:21:33 -07001556 mAggregationSuggestionEngine.setSuggestionsLimit(getResources().getInteger(
1557 R.integer.quickcontact_suggestions_limit));
Tingting Wange3a7c4f2015-09-24 18:23:18 -07001558 mAggregationSuggestionEngine.start();
1559 }
1560
1561 mAggregationSuggestionEngine.setContactId(mContactData.getId());
Tingting Wang18906c02015-10-16 14:53:10 -07001562 if (mPreviousContactId != 0
1563 && mPreviousContactId != mContactData.getId()) {
Tingting Wange29b6942015-09-30 15:21:33 -07001564 // Clear selected Ids when listing suggestions for new contact Id.
1565 mSelectedAggregationIds.clear();
1566 }
Tingting Wang18906c02015-10-16 14:53:10 -07001567 mPreviousContactId = mContactData.getId();
Tingting Wange3a7c4f2015-09-24 18:23:18 -07001568
1569 // Trigger suggestion engine to compute suggestions.
Tingting Wanga1af0c82015-10-23 10:53:58 -07001570 if (mContactData.getId() <= 0) {
1571 return;
1572 }
Tingting Wange3a7c4f2015-09-24 18:23:18 -07001573 final ContentValues values = new ContentValues();
1574 values.put(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
1575 mContactData.getDisplayName());
1576 values.put(ContactsContract.CommonDataKinds.StructuredName.PHONETIC_FAMILY_NAME,
1577 mContactData.getPhoneticName());
1578 mAggregationSuggestionEngine.onNameChange(ValuesDelta.fromBefore(values));
1579 }
1580
Wenyi Wanga26a57a2016-03-02 17:53:23 -08001581 private void populateContactAndAboutCard(Cp2DataCardModel cp2DataCardModel,
1582 boolean shouldAddPhoneticName) {
Brian Attwell6bb01342014-08-20 23:16:00 -07001583 mCachedCp2DataCardModel = cp2DataCardModel;
1584 if (mHasIntentLaunched || cp2DataCardModel == null) {
Paul Soulosa42ef762014-08-20 10:26:10 -07001585 return;
1586 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001587 Trace.beginSection("bind contact card");
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001588
Brian Attwell6bb01342014-08-20 23:16:00 -07001589 final List<List<Entry>> contactCardEntries = cp2DataCardModel.contactCardEntries;
1590 final List<List<Entry>> aboutCardEntries = cp2DataCardModel.aboutCardEntries;
1591 final String customAboutCardName = cp2DataCardModel.customAboutCardName;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001592
1593 if (contactCardEntries.size() > 0) {
1594 mContactCard.initialize(contactCardEntries,
1595 /* numInitialVisibleEntries = */ MIN_NUM_CONTACT_ENTRIES_SHOWN,
Paul Soulosc8e2a912014-07-21 14:52:20 -07001596 /* isExpanded = */ mContactCard.isExpanded(),
yaolu139a03b2016-09-02 17:44:10 -07001597 /* isAlwaysExpanded = */ true,
Paul Soulos0cda9ae2014-07-23 11:27:28 -07001598 mExpandingEntryCardViewListener,
yaolu139a03b2016-09-02 17:44:10 -07001599 mScroller);
yaolu79525d02016-08-24 12:08:39 -07001600 if (mContactCard.getVisibility() == View.GONE && mShouldLog) {
1601 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.CONTACT,
1602 ActionType.UNKNOWN_ACTION, /* thirdPartyAction */ null);
1603 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001604 mContactCard.setVisibility(View.VISIBLE);
1605 } else {
1606 mContactCard.setVisibility(View.GONE);
1607 }
1608 Trace.endSection();
1609
1610 Trace.beginSection("bind about card");
Paul Soulosc6e11092014-07-22 09:22:27 -07001611 // Phonetic name is not a data item, so the entry needs to be created separately
Wenyi Wanga26a57a2016-03-02 17:53:23 -08001612 // But if mCachedCp2DataCardModel is passed to this method (e.g. returning from editor
1613 // without saving any changes), then it should include phoneticName and the phoneticName
1614 // shouldn't be changed. If this is the case, we shouldn't add it again. b/27459294
Paul Soulosc6e11092014-07-22 09:22:27 -07001615 final String phoneticName = mContactData.getPhoneticName();
Wenyi Wanga26a57a2016-03-02 17:53:23 -08001616 if (shouldAddPhoneticName && !TextUtils.isEmpty(phoneticName)) {
Paul Soulosc6e11092014-07-22 09:22:27 -07001617 Entry phoneticEntry = new Entry(/* viewId = */ -1,
1618 /* icon = */ null,
1619 getResources().getString(R.string.name_phonetic),
1620 phoneticName,
Walter Jang8a3fe522014-10-16 11:12:13 -07001621 /* subHeaderIcon = */ null,
Paul Soulosc6e11092014-07-22 09:22:27 -07001622 /* text = */ null,
Walter Jang8a3fe522014-10-16 11:12:13 -07001623 /* textIcon = */ null,
Paul Soulos23e28362014-08-29 14:57:08 -07001624 /* primaryContentDescription = */ null,
Paul Soulosc6e11092014-07-22 09:22:27 -07001625 /* intent = */ null,
1626 /* alternateIcon = */ null,
1627 /* alternateIntent = */ null,
1628 /* alternateContentDescription = */ null,
1629 /* shouldApplyColor = */ false,
Paul Soulos2a4207f2014-07-31 17:09:05 -07001630 /* isEditable = */ false,
1631 /* EntryContextMenuInfo = */ new EntryContextMenuInfo(phoneticName,
Paul Soulos97f27802014-09-08 13:55:45 -07001632 getResources().getString(R.string.name_phonetic),
1633 /* mimeType = */ null, /* id = */ -1, /* isPrimary = */ false),
Paul Soulos48fc9122014-08-26 13:52:36 -07001634 /* thirdIcon = */ null,
1635 /* thirdIntent = */ null,
Paul Soulos48290be2014-09-08 13:44:51 -07001636 /* thirdContentDescription = */ null,
Tyler Gunn5f87e922015-08-05 14:24:52 -07001637 /* thirdAction = */ Entry.ACTION_NONE,
1638 /* thirdExtras = */ null,
1639 /* iconResourceId = */ 0);
Paul Soulosc6e11092014-07-22 09:22:27 -07001640 List<Entry> phoneticList = new ArrayList<>();
1641 phoneticList.add(phoneticEntry);
Paul Soulosa153dba2014-08-06 13:44:57 -07001642 // Phonetic name comes after nickname. Check to see if the first entry type is nickname
Paul Soulosa0fa4c12014-08-07 12:01:32 -07001643 if (aboutCardEntries.size() > 0 && aboutCardEntries.get(0).get(0).getHeader().equals(
Paul Soulosa153dba2014-08-06 13:44:57 -07001644 getResources().getString(R.string.header_nickname_entry))) {
1645 aboutCardEntries.add(1, phoneticList);
1646 } else {
1647 aboutCardEntries.add(0, phoneticList);
1648 }
Paul Soulosc6e11092014-07-22 09:22:27 -07001649 }
1650
Brian Attwell6bb01342014-08-20 23:16:00 -07001651 if (!TextUtils.isEmpty(customAboutCardName)) {
1652 mAboutCard.setTitle(customAboutCardName);
1653 }
1654
kungaox3b1a8b22015-02-05 15:29:19 +08001655 mAboutCard.initialize(aboutCardEntries,
1656 /* numInitialVisibleEntries = */ 1,
1657 /* isExpanded = */ true,
1658 /* isAlwaysExpanded = */ true,
1659 mExpandingEntryCardViewListener,
1660 mScroller);
Brian Attwell60953692014-07-11 17:18:46 -07001661
1662 if (contactCardEntries.size() == 0 && aboutCardEntries.size() == 0) {
1663 initializeNoContactDetailCard();
1664 } else {
1665 mNoContactDetailsCard.setVisibility(View.GONE);
1666 }
1667
Paul Soulosa8fce962014-07-15 13:37:24 -07001668 // If the Recent card is already initialized (all recent data is loaded), show the About
1669 // card if it has entries. Otherwise About card visibility will be set in bindRecentData()
yaolu79525d02016-08-24 12:08:39 -07001670 if (aboutCardEntries.size() > 0) {
1671 if (mAboutCard.getVisibility() == View.GONE && mShouldLog) {
1672 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.ABOUT,
1673 ActionType.UNKNOWN_ACTION, /* thirdPartyAction */ null);
1674 }
1675 if (isAllRecentDataLoaded()) {
1676 mAboutCard.setVisibility(View.VISIBLE);
1677 }
Paul Soulosa8fce962014-07-15 13:37:24 -07001678 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001679 Trace.endSection();
1680 }
1681
1682 /**
Brian Attwell60953692014-07-11 17:18:46 -07001683 * Create a card that shows "Add email" and "Add phone number" entries in grey.
1684 */
1685 private void initializeNoContactDetailCard() {
1686 final Drawable phoneIcon = getResources().getDrawable(
1687 R.drawable.ic_phone_24dp).mutate();
1688 final Entry phonePromptEntry = new Entry(CARD_ENTRY_ID_EDIT_CONTACT,
1689 phoneIcon, getString(R.string.quickcontact_add_phone_number),
Walter Jang8a3fe522014-10-16 11:12:13 -07001690 /* subHeader = */ null, /* subHeaderIcon = */ null, /* text = */ null,
1691 /* textIcon = */ null, /* primaryContentDescription = */ null,
Paul Soulos23e28362014-08-29 14:57:08 -07001692 getEditContactIntent(),
Paul Soulosdd7419d2014-07-15 11:22:13 -07001693 /* alternateIcon = */ null, /* alternateIntent = */ null,
Paul Soulos714455b2014-07-15 14:32:01 -07001694 /* alternateContentDescription = */ null, /* shouldApplyColor = */ true,
Paul Soulos48fc9122014-08-26 13:52:36 -07001695 /* isEditable = */ false, /* EntryContextMenuInfo = */ null,
1696 /* thirdIcon = */ null, /* thirdIntent = */ null,
Tyler Gunn5f87e922015-08-05 14:24:52 -07001697 /* thirdContentDescription = */ null,
1698 /* thirdAction = */ Entry.ACTION_NONE,
1699 /* thirdExtras = */ null,
1700 R.drawable.ic_phone_24dp);
Brian Attwell60953692014-07-11 17:18:46 -07001701
1702 final Drawable emailIcon = getResources().getDrawable(
1703 R.drawable.ic_email_24dp).mutate();
1704 final Entry emailPromptEntry = new Entry(CARD_ENTRY_ID_EDIT_CONTACT,
1705 emailIcon, getString(R.string.quickcontact_add_email), /* subHeader = */ null,
Walter Jang8a3fe522014-10-16 11:12:13 -07001706 /* subHeaderIcon = */ null,
1707 /* text = */ null, /* textIcon = */ null, /* primaryContentDescription = */ null,
Paul Soulos23e28362014-08-29 14:57:08 -07001708 getEditContactIntent(), /* alternateIcon = */ null,
Paul Soulosdd7419d2014-07-15 11:22:13 -07001709 /* alternateIntent = */ null, /* alternateContentDescription = */ null,
Paul Soulos2a4207f2014-07-31 17:09:05 -07001710 /* shouldApplyColor = */ true, /* isEditable = */ false,
Paul Soulos48fc9122014-08-26 13:52:36 -07001711 /* EntryContextMenuInfo = */ null, /* thirdIcon = */ null,
Paul Soulos48290be2014-09-08 13:44:51 -07001712 /* thirdIntent = */ null, /* thirdContentDescription = */ null,
Tyler Gunn5f87e922015-08-05 14:24:52 -07001713 /* thirdAction = */ Entry.ACTION_NONE, /* thirdExtras = */ null,
Paul Soulos48290be2014-09-08 13:44:51 -07001714 R.drawable.ic_email_24dp);
Brian Attwell60953692014-07-11 17:18:46 -07001715
1716 final List<List<Entry>> promptEntries = new ArrayList<>();
1717 promptEntries.add(new ArrayList<Entry>(1));
1718 promptEntries.add(new ArrayList<Entry>(1));
1719 promptEntries.get(0).add(phonePromptEntry);
1720 promptEntries.get(1).add(emailPromptEntry);
1721
1722 final int subHeaderTextColor = getResources().getColor(
1723 R.color.quickcontact_entry_sub_header_text_color);
1724 final PorterDuffColorFilter greyColorFilter =
1725 new PorterDuffColorFilter(subHeaderTextColor, PorterDuff.Mode.SRC_ATOP);
Paul Soulosc8e2a912014-07-21 14:52:20 -07001726 mNoContactDetailsCard.initialize(promptEntries, 2, /* isExpanded = */ true,
Paul Soulos0cda9ae2014-07-23 11:27:28 -07001727 /* isAlwaysExpanded = */ true, mExpandingEntryCardViewListener, mScroller);
yaolu79525d02016-08-24 12:08:39 -07001728 if (mNoContactDetailsCard.getVisibility() == View.GONE && mShouldLog) {
1729 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.NO_CONTACT,
1730 ActionType.UNKNOWN_ACTION, /* thirdPartyAction */ null);
1731 }
Brian Attwell60953692014-07-11 17:18:46 -07001732 mNoContactDetailsCard.setVisibility(View.VISIBLE);
1733 mNoContactDetailsCard.setEntryHeaderColor(subHeaderTextColor);
1734 mNoContactDetailsCard.setColorAndFilter(subHeaderTextColor, greyColorFilter);
1735 }
1736
1737 /**
Paul Souloseb64a4b2014-07-07 17:03:27 -07001738 * Builds the {@link DataItem}s Map out of the Contact.
1739 * @param data The contact to build the data from.
1740 * @return A pair containing a list of data items sorted within mimetype and sorted
1741 * amongst mimetype. The map goes from mimetype string to the sorted list of data items within
1742 * mimetype
1743 */
Brian Attwell6bb01342014-08-20 23:16:00 -07001744 private Cp2DataCardModel generateDataModelFromContact(
Paul Souloseb64a4b2014-07-07 17:03:27 -07001745 Contact data) {
1746 Trace.beginSection("Build data items map");
1747
1748 final Map<String, List<DataItem>> dataItemsMap = new HashMap<>();
Brian Attwell8bf96e72014-06-25 15:46:47 -07001749
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001750 final ResolveCache cache = ResolveCache.getInstance(this);
Maurice Chu851222a2012-06-21 11:43:08 -07001751 for (RawContact rawContact : data.getRawContacts()) {
1752 for (DataItem dataItem : rawContact.getDataItems()) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07001753 dataItem.setRawContactId(rawContact.getId());
1754
Maurice Chu851222a2012-06-21 11:43:08 -07001755 final String mimeType = dataItem.getMimeType();
Paul Souloseb64a4b2014-07-07 17:03:27 -07001756 if (mimeType == null) continue;
1757
Chiao Cheng47b6f702012-09-07 17:28:17 -07001758 final AccountType accountType = rawContact.getAccountType(this);
1759 final DataKind dataKind = AccountTypeManager.getInstance(this)
1760 .getKindOrFallback(accountType, mimeType);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001761 if (dataKind == null) continue;
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001762
Paul Souloseb64a4b2014-07-07 17:03:27 -07001763 dataItem.setDataKind(dataKind);
1764
1765 final boolean hasData = !TextUtils.isEmpty(dataItem.buildDataString(this,
1766 dataKind));
1767
1768 if (isMimeExcluded(mimeType) || !hasData) continue;
1769
1770 List<DataItem> dataItemListByType = dataItemsMap.get(mimeType);
1771 if (dataItemListByType == null) {
1772 dataItemListByType = new ArrayList<>();
1773 dataItemsMap.put(mimeType, dataItemListByType);
Paul Soulosb3054e52014-06-05 16:46:02 -07001774 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07001775 dataItemListByType.add(dataItem);
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001776 }
1777 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07001778 Trace.endSection();
Makoto Onukidfe8cc82012-05-17 18:03:44 -07001779
Paul Souloseb64a4b2014-07-07 17:03:27 -07001780 Trace.beginSection("sort within mimetypes");
Paul Soulos16339332014-06-24 13:59:56 -07001781 /*
1782 * 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 -07001783 * used data items, one per mimetype. Then, within each mimetype, the list of data items
1784 * for that type is also sorted, based off of {super primary, primary, times used} in that
1785 * order.
Paul Soulos16339332014-06-24 13:59:56 -07001786 */
Paul Souloseb64a4b2014-07-07 17:03:27 -07001787 final List<List<DataItem>> dataItemsList = new ArrayList<>();
1788 for (List<DataItem> mimeTypeDataItems : dataItemsMap.values()) {
1789 // Remove duplicate data items
1790 Collapser.collapseList(mimeTypeDataItems, this);
1791 // Sort within mimetype
1792 Collections.sort(mimeTypeDataItems, mWithinMimeTypeDataItemComparator);
1793 // Add to the list of data item lists
1794 dataItemsList.add(mimeTypeDataItems);
1795 }
1796 Trace.endSection();
Paul Soulos16339332014-06-24 13:59:56 -07001797
Paul Souloseb64a4b2014-07-07 17:03:27 -07001798 Trace.beginSection("sort amongst mimetypes");
1799 // Sort amongst mimetypes to bubble up the top data items for the contact card
1800 Collections.sort(dataItemsList, mAmongstMimeTypeDataItemComparator);
1801 Trace.endSection();
1802
Brian Attwell6bb01342014-08-20 23:16:00 -07001803 Trace.beginSection("cp2 data items to entries");
1804
1805 final List<List<Entry>> contactCardEntries = new ArrayList<>();
1806 final List<List<Entry>> aboutCardEntries = buildAboutCardEntries(dataItemsMap);
1807 final MutableString aboutCardName = new MutableString();
1808
1809 for (int i = 0; i < dataItemsList.size(); ++i) {
1810 final List<DataItem> dataItemsByMimeType = dataItemsList.get(i);
1811 final DataItem topDataItem = dataItemsByMimeType.get(0);
1812 if (SORTED_ABOUT_CARD_MIMETYPES.contains(topDataItem.getMimeType())) {
1813 // About card mimetypes are built in buildAboutCardEntries, skip here
1814 continue;
1815 } else {
1816 List<Entry> contactEntries = dataItemsToEntries(dataItemsList.get(i),
1817 aboutCardName);
1818 if (contactEntries.size() > 0) {
1819 contactCardEntries.add(contactEntries);
1820 }
1821 }
1822 }
1823
1824 Trace.endSection();
1825
1826 final Cp2DataCardModel dataModel = new Cp2DataCardModel();
1827 dataModel.customAboutCardName = aboutCardName.value;
1828 dataModel.aboutCardEntries = aboutCardEntries;
1829 dataModel.contactCardEntries = contactCardEntries;
1830 dataModel.dataItemsMap = dataItemsMap;
1831 return dataModel;
1832 }
1833
1834 /**
1835 * Class used to hold the About card and Contact cards' data model that gets generated
1836 * on a background thread. All data is from CP2.
1837 */
1838 private static class Cp2DataCardModel {
1839 /**
1840 * A map between a mimetype string and the corresponding list of data items. The data items
1841 * are in sorted order using mWithinMimeTypeDataItemComparator.
1842 */
1843 public Map<String, List<DataItem>> dataItemsMap;
1844 public List<List<Entry>> aboutCardEntries;
1845 public List<List<Entry>> contactCardEntries;
1846 public String customAboutCardName;
1847 }
1848
1849 private static class MutableString {
1850 public String value;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001851 }
1852
1853 /**
1854 * Converts a {@link DataItem} into an {@link ExpandingEntryCardView.Entry} for display.
1855 * If the {@link ExpandingEntryCardView.Entry} has no visual elements, null is returned.
Brian Attwell6bb01342014-08-20 23:16:00 -07001856 *
1857 * This runs on a background thread. This is set as static to avoid accidentally adding
1858 * additional dependencies on unsafe things (like the Activity).
1859 *
Paul Souloseb64a4b2014-07-07 17:03:27 -07001860 * @param dataItem The {@link DataItem} to convert.
Paul Soulos6a2a1a72014-10-10 15:52:56 -07001861 * @param secondDataItem A second {@link DataItem} to help build a full entry for some
1862 * mimetypes
Paul Souloseb64a4b2014-07-07 17:03:27 -07001863 * @return The {@link ExpandingEntryCardView.Entry}, or null if no visual elements are present.
1864 */
Paul Soulos6a2a1a72014-10-10 15:52:56 -07001865 private static Entry dataItemToEntry(DataItem dataItem, DataItem secondDataItem,
Brian Attwell6bb01342014-08-20 23:16:00 -07001866 Context context, Contact contactData,
1867 final MutableString aboutCardName) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07001868 Drawable icon = null;
1869 String header = null;
1870 String subHeader = null;
1871 Drawable subHeaderIcon = null;
1872 String text = null;
1873 Drawable textIcon = null;
Paul Soulos23e28362014-08-29 14:57:08 -07001874 StringBuilder primaryContentDescription = new StringBuilder();
Tingting Wangb6949dc2015-11-03 13:17:12 -08001875 Spannable phoneContentDescription = null;
Wenyi Wang5da55ff2015-11-19 13:22:40 -08001876 Spannable smsContentDescription = null;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001877 Intent intent = null;
Paul Soulos48ebbaa2014-07-15 13:11:23 -07001878 boolean shouldApplyColor = true;
Paul Soulosdd7419d2014-07-15 11:22:13 -07001879 Drawable alternateIcon = null;
1880 Intent alternateIntent = null;
Paul Soulos23e28362014-08-29 14:57:08 -07001881 StringBuilder alternateContentDescription = new StringBuilder();
Paul Souloseb64a4b2014-07-07 17:03:27 -07001882 final boolean isEditable = false;
Paul Soulos2a4207f2014-07-31 17:09:05 -07001883 EntryContextMenuInfo entryContextMenuInfo = null;
Paul Soulos48fc9122014-08-26 13:52:36 -07001884 Drawable thirdIcon = null;
1885 Intent thirdIntent = null;
Tyler Gunn5f87e922015-08-05 14:24:52 -07001886 int thirdAction = Entry.ACTION_NONE;
Paul Soulos48fc9122014-08-26 13:52:36 -07001887 String thirdContentDescription = null;
Tyler Gunn5f87e922015-08-05 14:24:52 -07001888 Bundle thirdExtras = null;
Paul Soulos48290be2014-09-08 13:44:51 -07001889 int iconResourceId = 0;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001890
Brian Attwell6bb01342014-08-20 23:16:00 -07001891 context = context.getApplicationContext();
Paul Soulos23e28362014-08-29 14:57:08 -07001892 final Resources res = context.getResources();
Paul Souloseb64a4b2014-07-07 17:03:27 -07001893 DataKind kind = dataItem.getDataKind();
1894
1895 if (dataItem instanceof ImDataItem) {
1896 final ImDataItem im = (ImDataItem) dataItem;
Brian Attwell6bb01342014-08-20 23:16:00 -07001897 intent = ContactsUtils.buildImIntent(context, im).first;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001898 final boolean isEmail = im.isCreatedFromEmail();
Paul Soulos7de6f852014-07-28 14:07:28 -07001899 final int protocol;
1900 if (!im.isProtocolValid()) {
1901 protocol = Im.PROTOCOL_CUSTOM;
1902 } else {
1903 protocol = isEmail ? Im.PROTOCOL_GOOGLE_TALK : im.getProtocol();
1904 }
1905 if (protocol == Im.PROTOCOL_CUSTOM) {
1906 // If the protocol is custom, display the "IM" entry header as well to distinguish
1907 // this entry from other ones
Paul Soulos23e28362014-08-29 14:57:08 -07001908 header = res.getString(R.string.header_im_entry);
1909 subHeader = Im.getProtocolLabel(res, protocol,
Paul Soulos7de6f852014-07-28 14:07:28 -07001910 im.getCustomProtocol()).toString();
1911 text = im.getData();
1912 } else {
Paul Soulos23e28362014-08-29 14:57:08 -07001913 header = Im.getProtocolLabel(res, protocol,
Paul Soulos7de6f852014-07-28 14:07:28 -07001914 im.getCustomProtocol()).toString();
1915 subHeader = im.getData();
1916 }
Paul Soulos97f27802014-09-08 13:55:45 -07001917 entryContextMenuInfo = new EntryContextMenuInfo(im.getData(), header,
1918 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001919 } else if (dataItem instanceof OrganizationDataItem) {
1920 final OrganizationDataItem organization = (OrganizationDataItem) dataItem;
Paul Soulos23e28362014-08-29 14:57:08 -07001921 header = res.getString(R.string.header_organization_entry);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001922 subHeader = organization.getCompany();
Paul Soulos97f27802014-09-08 13:55:45 -07001923 entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header,
1924 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001925 text = organization.getTitle();
1926 } else if (dataItem instanceof NicknameDataItem) {
1927 final NicknameDataItem nickname = (NicknameDataItem) dataItem;
1928 // Build nickname entries
1929 final boolean isNameRawContact =
Brian Attwell6bb01342014-08-20 23:16:00 -07001930 (contactData.getNameRawContactId() == dataItem.getRawContactId());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001931
1932 final boolean duplicatesTitle =
1933 isNameRawContact
Brian Attwell6bb01342014-08-20 23:16:00 -07001934 && contactData.getDisplayNameSource() == DisplayNameSources.NICKNAME;
Paul Souloseb64a4b2014-07-07 17:03:27 -07001935
1936 if (!duplicatesTitle) {
Paul Soulos23e28362014-08-29 14:57:08 -07001937 header = res.getString(R.string.header_nickname_entry);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001938 subHeader = nickname.getName();
Paul Soulos97f27802014-09-08 13:55:45 -07001939 entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header,
1940 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001941 }
yaolu1bd88262016-08-18 10:29:12 -07001942 } else if (dataItem instanceof CustomDataItem) {
1943 final CustomDataItem customDataItem = (CustomDataItem) dataItem;
1944 final String summary = customDataItem.getSummary();
1945 header = TextUtils.isEmpty(summary)
1946 ? res.getString(R.string.label_custom_field) : summary;
1947 subHeader = customDataItem.getContent();
1948 entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header,
1949 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001950 } else if (dataItem instanceof NoteDataItem) {
1951 final NoteDataItem note = (NoteDataItem) dataItem;
Paul Soulos23e28362014-08-29 14:57:08 -07001952 header = res.getString(R.string.header_note_entry);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001953 subHeader = note.getNote();
Paul Soulos97f27802014-09-08 13:55:45 -07001954 entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header,
1955 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001956 } else if (dataItem instanceof WebsiteDataItem) {
1957 final WebsiteDataItem website = (WebsiteDataItem) dataItem;
Paul Soulos23e28362014-08-29 14:57:08 -07001958 header = res.getString(R.string.header_website_entry);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001959 subHeader = website.getUrl();
Paul Soulos97f27802014-09-08 13:55:45 -07001960 entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header,
1961 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001962 try {
Brian Attwell3bcf09e2014-12-08 16:16:05 -08001963 final WebAddress webAddress = new WebAddress(website.buildDataStringForDisplay
1964 (context, kind));
Paul Souloseb64a4b2014-07-07 17:03:27 -07001965 intent = new Intent(Intent.ACTION_VIEW, Uri.parse(webAddress.toString()));
1966 } catch (final ParseException e) {
Brian Attwell3bcf09e2014-12-08 16:16:05 -08001967 Log.e(TAG, "Couldn't parse website: " + website.buildDataStringForDisplay(
1968 context, kind));
Paul Souloseb64a4b2014-07-07 17:03:27 -07001969 }
1970 } else if (dataItem instanceof EventDataItem) {
1971 final EventDataItem event = (EventDataItem) dataItem;
Brian Attwell3bcf09e2014-12-08 16:16:05 -08001972 final String dataString = event.buildDataStringForDisplay(context, kind);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001973 final Calendar cal = DateUtils.parseDate(dataString, false);
1974 if (cal != null) {
1975 final Date nextAnniversary =
1976 DateUtils.getNextAnnualDate(cal);
1977 final Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
1978 builder.appendPath("time");
1979 ContentUris.appendId(builder, nextAnniversary.getTime());
1980 intent = new Intent(Intent.ACTION_VIEW).setData(builder.build());
1981 }
Paul Soulos23e28362014-08-29 14:57:08 -07001982 header = res.getString(R.string.header_event_entry);
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001983 if (event.hasKindTypeColumn(kind)) {
Wenyi Wangc85af282015-12-21 15:55:32 -08001984 subHeader = EventCompat.getTypeLabel(res, event.getKindTypeColumn(kind),
Paul Soulos7de6f852014-07-28 14:07:28 -07001985 event.getLabel()).toString();
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07001986 }
Brian Attwell6bb01342014-08-20 23:16:00 -07001987 text = DateUtils.formatDate(context, dataString);
Paul Soulos97f27802014-09-08 13:55:45 -07001988 entryContextMenuInfo = new EntryContextMenuInfo(text, header,
1989 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Souloseb64a4b2014-07-07 17:03:27 -07001990 } else if (dataItem instanceof RelationDataItem) {
1991 final RelationDataItem relation = (RelationDataItem) dataItem;
Brian Attwell3bcf09e2014-12-08 16:16:05 -08001992 final String dataString = relation.buildDataStringForDisplay(context, kind);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001993 if (!TextUtils.isEmpty(dataString)) {
1994 intent = new Intent(Intent.ACTION_SEARCH);
1995 intent.putExtra(SearchManager.QUERY, dataString);
1996 intent.setType(Contacts.CONTENT_TYPE);
1997 }
Paul Soulos23e28362014-08-29 14:57:08 -07001998 header = res.getString(R.string.header_relation_entry);
Paul Souloseb64a4b2014-07-07 17:03:27 -07001999 subHeader = relation.getName();
Paul Soulos97f27802014-09-08 13:55:45 -07002000 entryContextMenuInfo = new EntryContextMenuInfo(subHeader, header,
2001 dataItem.getMimeType(), dataItem.getId(), dataItem.isSuperPrimary());
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07002002 if (relation.hasKindTypeColumn(kind)) {
Paul Soulos23e28362014-08-29 14:57:08 -07002003 text = Relation.getTypeLabel(res,
Brian Attwell6bb01342014-08-20 23:16:00 -07002004 relation.getKindTypeColumn(kind),
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07002005 relation.getLabel()).toString();
2006 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07002007 } else if (dataItem instanceof PhoneDataItem) {
2008 final PhoneDataItem phone = (PhoneDataItem) dataItem;
Tyler Gunn5f87e922015-08-05 14:24:52 -07002009 String phoneLabel = null;
Paul Souloseb64a4b2014-07-07 17:03:27 -07002010 if (!TextUtils.isEmpty(phone.getNumber())) {
Paul Soulos23e28362014-08-29 14:57:08 -07002011 primaryContentDescription.append(res.getString(R.string.call_other)).append(" ");
Brian Attwell3bcf09e2014-12-08 16:16:05 -08002012 header = sBidiFormatter.unicodeWrap(phone.buildDataStringForDisplay(context, kind),
Brian Attwellc62cc792014-10-02 12:35:07 -07002013 TextDirectionHeuristics.LTR);
Paul Soulos2a4207f2014-07-31 17:09:05 -07002014 entryContextMenuInfo = new EntryContextMenuInfo(header,
Paul Soulos97f27802014-09-08 13:55:45 -07002015 res.getString(R.string.phoneLabelsGroup), dataItem.getMimeType(),
2016 dataItem.getId(), dataItem.isSuperPrimary());
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07002017 if (phone.hasKindTypeColumn(kind)) {
Tingting Wang01b37682015-07-17 14:09:19 -07002018 final int kindTypeColumn = phone.getKindTypeColumn(kind);
2019 final String label = phone.getLabel();
Tyler Gunn5f87e922015-08-05 14:24:52 -07002020 phoneLabel = label;
Tingting Wang01b37682015-07-17 14:09:19 -07002021 if (kindTypeColumn == Phone.TYPE_CUSTOM && TextUtils.isEmpty(label)) {
2022 text = "";
2023 } else {
2024 text = Phone.getTypeLabel(res, kindTypeColumn, label).toString();
Tyler Gunn5f87e922015-08-05 14:24:52 -07002025 phoneLabel= text;
Tingting Wang3df785b2015-07-07 16:53:14 -07002026 primaryContentDescription.append(text).append(" ");
2027 }
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07002028 }
Paul Soulos23e28362014-08-29 14:57:08 -07002029 primaryContentDescription.append(header);
Tingting Wangb6949dc2015-11-03 13:17:12 -08002030 phoneContentDescription = com.android.contacts.common.util.ContactDisplayUtils
2031 .getTelephoneTtsSpannable(primaryContentDescription.toString(), header);
Paul Soulos23e28362014-08-29 14:57:08 -07002032 icon = res.getDrawable(R.drawable.ic_phone_24dp);
Paul Soulos48290be2014-09-08 13:44:51 -07002033 iconResourceId = R.drawable.ic_phone_24dp;
Brian Attwell6bb01342014-08-20 23:16:00 -07002034 if (PhoneCapabilityTester.isPhone(context)) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07002035 intent = CallUtil.getCallIntent(phone.getNumber());
yaolu79525d02016-08-24 12:08:39 -07002036 intent.putExtra(EXTRA_ACTION_TYPE, ActionType.CALL);
Paul Soulos16339332014-06-24 13:59:56 -07002037 }
Paul Soulosdd7419d2014-07-15 11:22:13 -07002038 alternateIntent = new Intent(Intent.ACTION_SENDTO,
Jay Shrauner1cd88e32014-09-05 15:37:55 -07002039 Uri.fromParts(ContactsUtils.SCHEME_SMSTO, phone.getNumber(), null));
yaolu79525d02016-08-24 12:08:39 -07002040 alternateIntent.putExtra(EXTRA_ACTION_TYPE, ActionType.SMS);
Paul Soulos48fc9122014-08-26 13:52:36 -07002041
Walter Jang3133a532016-05-04 17:51:19 -07002042 alternateIcon = res.getDrawable(R.drawable.ic_message_24dp_mirrored);
Paul Soulos23e28362014-08-29 14:57:08 -07002043 alternateContentDescription.append(res.getString(R.string.sms_custom, header));
Wenyi Wang5da55ff2015-11-19 13:22:40 -08002044 smsContentDescription = com.android.contacts.common.util.ContactDisplayUtils
2045 .getTelephoneTtsSpannable(alternateContentDescription.toString(), header);
Paul Soulos48fc9122014-08-26 13:52:36 -07002046
Tyler Gunn0a8f9732015-12-15 15:38:54 -08002047 int videoCapability = CallUtil.getVideoCallingAvailability(context);
2048 boolean isPresenceEnabled =
2049 (videoCapability & CallUtil.VIDEO_CALLING_PRESENCE) != 0;
2050 boolean isVideoEnabled = (videoCapability & CallUtil.VIDEO_CALLING_ENABLED) != 0;
2051
Tyler Gunn5f87e922015-08-05 14:24:52 -07002052 if (CallUtil.isCallWithSubjectSupported(context)) {
2053 thirdIcon = res.getDrawable(R.drawable.ic_call_note_white_24dp);
2054 thirdAction = Entry.ACTION_CALL_WITH_SUBJECT;
2055 thirdContentDescription =
2056 res.getString(R.string.call_with_a_note);
Tyler Gunn5f87e922015-08-05 14:24:52 -07002057 // Create a bundle containing the data the call subject dialog requires.
2058 thirdExtras = new Bundle();
2059 thirdExtras.putLong(CallSubjectDialog.ARG_PHOTO_ID,
2060 contactData.getPhotoId());
2061 thirdExtras.putParcelable(CallSubjectDialog.ARG_PHOTO_URI,
2062 UriUtils.parseUriOrNull(contactData.getPhotoUri()));
2063 thirdExtras.putParcelable(CallSubjectDialog.ARG_CONTACT_URI,
2064 contactData.getLookupUri());
2065 thirdExtras.putString(CallSubjectDialog.ARG_NAME_OR_NUMBER,
2066 contactData.getDisplayName());
2067 thirdExtras.putBoolean(CallSubjectDialog.ARG_IS_BUSINESS, false);
2068 thirdExtras.putString(CallSubjectDialog.ARG_NUMBER,
2069 phone.getNumber());
2070 thirdExtras.putString(CallSubjectDialog.ARG_DISPLAY_NUMBER,
2071 phone.getFormattedPhoneNumber());
2072 thirdExtras.putString(CallSubjectDialog.ARG_NUMBER_LABEL,
2073 phoneLabel);
Tyler Gunn0a8f9732015-12-15 15:38:54 -08002074 } else if (isVideoEnabled) {
2075 // Check to ensure carrier presence indicates the number supports video calling.
2076 int carrierPresence = dataItem.getCarrierPresence();
2077 boolean isPresent = (carrierPresence & Phone.CARRIER_PRESENCE_VT_CAPABLE) != 0;
2078
2079 if ((isPresenceEnabled && isPresent) || !isPresenceEnabled) {
2080 thirdIcon = res.getDrawable(R.drawable.ic_videocam);
2081 thirdAction = Entry.ACTION_INTENT;
2082 thirdIntent = CallUtil.getVideoCallIntent(phone.getNumber(),
2083 CALL_ORIGIN_QUICK_CONTACTS_ACTIVITY);
yaolu79525d02016-08-24 12:08:39 -07002084 thirdIntent.putExtra(EXTRA_ACTION_TYPE, ActionType.VIDEOCALL);
Tyler Gunn0a8f9732015-12-15 15:38:54 -08002085 thirdContentDescription =
2086 res.getString(R.string.description_video_call);
2087 }
Paul Soulos48fc9122014-08-26 13:52:36 -07002088 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07002089 }
2090 } else if (dataItem instanceof EmailDataItem) {
2091 final EmailDataItem email = (EmailDataItem) dataItem;
2092 final String address = email.getData();
2093 if (!TextUtils.isEmpty(address)) {
Paul Soulos23e28362014-08-29 14:57:08 -07002094 primaryContentDescription.append(res.getString(R.string.email_other)).append(" ");
Jay Shrauner1cd88e32014-09-05 15:37:55 -07002095 final Uri mailUri = Uri.fromParts(ContactsUtils.SCHEME_MAILTO, address, null);
Paul Souloseb64a4b2014-07-07 17:03:27 -07002096 intent = new Intent(Intent.ACTION_SENDTO, mailUri);
yaolu79525d02016-08-24 12:08:39 -07002097 intent.putExtra(EXTRA_ACTION_TYPE, ActionType.EMAIL);
Paul Souloseb64a4b2014-07-07 17:03:27 -07002098 header = email.getAddress();
Paul Soulos2a4207f2014-07-31 17:09:05 -07002099 entryContextMenuInfo = new EntryContextMenuInfo(header,
Paul Soulos97f27802014-09-08 13:55:45 -07002100 res.getString(R.string.emailLabelsGroup), dataItem.getMimeType(),
2101 dataItem.getId(), dataItem.isSuperPrimary());
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07002102 if (email.hasKindTypeColumn(kind)) {
Paul Soulos23e28362014-08-29 14:57:08 -07002103 text = Email.getTypeLabel(res, email.getKindTypeColumn(kind),
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07002104 email.getLabel()).toString();
Paul Soulos23e28362014-08-29 14:57:08 -07002105 primaryContentDescription.append(text).append(" ");
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07002106 }
Paul Soulos23e28362014-08-29 14:57:08 -07002107 primaryContentDescription.append(header);
2108 icon = res.getDrawable(R.drawable.ic_email_24dp);
Paul Soulos48290be2014-09-08 13:44:51 -07002109 iconResourceId = R.drawable.ic_email_24dp;
Paul Souloseb64a4b2014-07-07 17:03:27 -07002110 }
2111 } else if (dataItem instanceof StructuredPostalDataItem) {
2112 StructuredPostalDataItem postal = (StructuredPostalDataItem) dataItem;
2113 final String postalAddress = postal.getFormattedAddress();
2114 if (!TextUtils.isEmpty(postalAddress)) {
Paul Soulos23e28362014-08-29 14:57:08 -07002115 primaryContentDescription.append(res.getString(R.string.map_other)).append(" ");
Paul Souloseb64a4b2014-07-07 17:03:27 -07002116 intent = StructuredPostalUtils.getViewPostalAddressIntent(postalAddress);
yaolu79525d02016-08-24 12:08:39 -07002117 intent.putExtra(EXTRA_ACTION_TYPE, ActionType.MAP);
Paul Souloseb64a4b2014-07-07 17:03:27 -07002118 header = postal.getFormattedAddress();
Paul Soulos2a4207f2014-07-31 17:09:05 -07002119 entryContextMenuInfo = new EntryContextMenuInfo(header,
Paul Soulos97f27802014-09-08 13:55:45 -07002120 res.getString(R.string.postalLabelsGroup), dataItem.getMimeType(),
2121 dataItem.getId(), dataItem.isSuperPrimary());
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07002122 if (postal.hasKindTypeColumn(kind)) {
Paul Soulos23e28362014-08-29 14:57:08 -07002123 text = StructuredPostal.getTypeLabel(res,
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07002124 postal.getKindTypeColumn(kind), postal.getLabel()).toString();
Paul Soulos23e28362014-08-29 14:57:08 -07002125 primaryContentDescription.append(text).append(" ");
Paul Soulosf9d5c0e2014-07-09 11:25:59 -07002126 }
Paul Soulos23e28362014-08-29 14:57:08 -07002127 primaryContentDescription.append(header);
Paul Soulos6a4d2732014-07-22 14:22:12 -07002128 alternateIntent =
2129 StructuredPostalUtils.getViewPostalAddressDirectionsIntent(postalAddress);
yaolu79525d02016-08-24 12:08:39 -07002130 alternateIntent.putExtra(EXTRA_ACTION_TYPE, ActionType.DIRECTIONS);
Paul Soulos23e28362014-08-29 14:57:08 -07002131 alternateIcon = res.getDrawable(R.drawable.ic_directions_24dp);
2132 alternateContentDescription.append(res.getString(
2133 R.string.content_description_directions)).append(" ").append(header);
2134 icon = res.getDrawable(R.drawable.ic_place_24dp);
Paul Soulos48290be2014-09-08 13:44:51 -07002135 iconResourceId = R.drawable.ic_place_24dp;
Paul Souloseb64a4b2014-07-07 17:03:27 -07002136 }
2137 } else if (dataItem instanceof SipAddressDataItem) {
Brian Attwell593e5a72014-10-22 19:45:28 -07002138 final SipAddressDataItem sip = (SipAddressDataItem) dataItem;
2139 final String address = sip.getSipAddress();
2140 if (!TextUtils.isEmpty(address)) {
2141 primaryContentDescription.append(res.getString(R.string.call_other)).append(
2142 " ");
2143 if (PhoneCapabilityTester.isSipPhone(context)) {
Jay Shrauner1cd88e32014-09-05 15:37:55 -07002144 final Uri callUri = Uri.fromParts(PhoneAccount.SCHEME_SIP, address, null);
Paul Souloseb64a4b2014-07-07 17:03:27 -07002145 intent = CallUtil.getCallIntent(callUri);
yaolu79525d02016-08-24 12:08:39 -07002146 intent.putExtra(EXTRA_ACTION_TYPE, ActionType.SIPCALL);
Paul Souloseb64a4b2014-07-07 17:03:27 -07002147 }
Brian Attwell593e5a72014-10-22 19:45:28 -07002148 header = address;
2149 entryContextMenuInfo = new EntryContextMenuInfo(header,
2150 res.getString(R.string.phoneLabelsGroup), dataItem.getMimeType(),
2151 dataItem.getId(), dataItem.isSuperPrimary());
2152 if (sip.hasKindTypeColumn(kind)) {
2153 text = SipAddress.getTypeLabel(res,
2154 sip.getKindTypeColumn(kind), sip.getLabel()).toString();
2155 primaryContentDescription.append(text).append(" ");
2156 }
2157 primaryContentDescription.append(header);
2158 icon = res.getDrawable(R.drawable.ic_dialer_sip_black_24dp);
2159 iconResourceId = R.drawable.ic_dialer_sip_black_24dp;
Paul Souloseb64a4b2014-07-07 17:03:27 -07002160 }
2161 } else if (dataItem instanceof StructuredNameDataItem) {
Walter Jang8025f802015-02-27 14:18:14 -08002162 // If the name is already set and this is not the super primary value then leave the
2163 // current value. This way we show the super primary value when we are able to.
2164 if (dataItem.isSuperPrimary() || aboutCardName.value == null
2165 || aboutCardName.value.isEmpty()) {
2166 final String givenName = ((StructuredNameDataItem) dataItem).getGivenName();
2167 if (!TextUtils.isEmpty(givenName)) {
2168 aboutCardName.value = res.getString(R.string.about_card_title) +
2169 " " + givenName;
2170 } else {
2171 aboutCardName.value = res.getString(R.string.about_card_title);
2172 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07002173 }
2174 } else {
2175 // Custom DataItem
Brian Attwell6bb01342014-08-20 23:16:00 -07002176 header = dataItem.buildDataStringForDisplay(context, kind);
Paul Souloseb64a4b2014-07-07 17:03:27 -07002177 text = kind.typeColumn;
2178 intent = new Intent(Intent.ACTION_VIEW);
Paul Soulos60e51082014-07-10 12:33:04 -07002179 final Uri uri = ContentUris.withAppendedId(Data.CONTENT_URI, dataItem.getId());
2180 intent.setDataAndType(uri, dataItem.getMimeType());
yaolu79525d02016-08-24 12:08:39 -07002181 intent.putExtra(EXTRA_ACTION_TYPE, ActionType.THIRD_PARTY);
2182 intent.putExtra(EXTRA_THIRD_PARTY_ACTION, header);
Paul Soulose0055662014-07-10 16:33:08 -07002183
2184 if (intent != null) {
2185 final String mimetype = intent.getType();
2186
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002187 // Build advanced entry for known 3p types. Otherwise default to ResolveCache icon.
yaolu8c3ff2c2016-08-18 20:13:40 -07002188 if (MIMETYPE_HANGOUTS.equals(mimetype)) {
2189 // If a secondDataItem is available, use it to build an entry with
2190 // alternate actions
2191 if (secondDataItem != null) {
2192 icon = res.getDrawable(R.drawable.ic_hangout_24dp);
2193 alternateIcon = res.getDrawable(R.drawable.ic_hangout_video_24dp);
2194 final HangoutsDataItemModel itemModel =
2195 new HangoutsDataItemModel(intent, alternateIntent,
2196 dataItem, secondDataItem, alternateContentDescription,
2197 header, text, context);
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002198
yaolu8c3ff2c2016-08-18 20:13:40 -07002199 populateHangoutsDataItemModel(itemModel);
2200 intent = itemModel.intent;
2201 alternateIntent = itemModel.alternateIntent;
2202 alternateContentDescription = itemModel.alternateContentDescription;
2203 header = itemModel.header;
2204 text = itemModel.text;
2205 } else {
2206 if (HANGOUTS_DATA_5_VIDEO.equals(intent.getDataString())) {
2207 icon = res.getDrawable(R.drawable.ic_hangout_video_24dp);
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002208 } else {
yaolu8c3ff2c2016-08-18 20:13:40 -07002209 icon = res.getDrawable(R.drawable.ic_hangout_24dp);
Paul Soulose0055662014-07-10 16:33:08 -07002210 }
yaolu8c3ff2c2016-08-18 20:13:40 -07002211 }
2212 } else {
2213 icon = ResolveCache.getInstance(context).getIcon(
2214 dataItem.getMimeType(), intent);
2215 // Call mutate to create a new Drawable.ConstantState for color filtering
2216 if (icon != null) {
2217 icon.mutate();
2218 }
2219 shouldApplyColor = false;
2220
2221 if (!MIMETYPE_GPLUS_PROFILE.equals(mimetype)) {
Paul Soulos97f27802014-09-08 13:55:45 -07002222 entryContextMenuInfo = new EntryContextMenuInfo(header, mimetype,
2223 dataItem.getMimeType(), dataItem.getId(),
2224 dataItem.isSuperPrimary());
yaolu8c3ff2c2016-08-18 20:13:40 -07002225 }
Paul Soulose0055662014-07-10 16:33:08 -07002226 }
2227 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -07002228 }
2229
Paul Souloseb64a4b2014-07-07 17:03:27 -07002230 if (intent != null) {
2231 // Do not set the intent is there are no resolves
Brian Attwell6bb01342014-08-20 23:16:00 -07002232 if (!PhoneCapabilityTester.isIntentRegistered(context, intent)) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07002233 intent = null;
Paul Soulos16339332014-06-24 13:59:56 -07002234 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07002235 }
Paul Soulos16339332014-06-24 13:59:56 -07002236
Paul Soulosdd7419d2014-07-15 11:22:13 -07002237 if (alternateIntent != null) {
2238 // Do not set the alternate intent is there are no resolves
Brian Attwell6bb01342014-08-20 23:16:00 -07002239 if (!PhoneCapabilityTester.isIntentRegistered(context, alternateIntent)) {
Paul Soulosdd7419d2014-07-15 11:22:13 -07002240 alternateIntent = null;
Jay Shraunerf0d04c12014-09-02 13:44:48 -07002241 } else if (TextUtils.isEmpty(alternateContentDescription)) {
2242 // Attempt to use package manager to find a suitable content description if needed
Paul Soulos23e28362014-08-29 14:57:08 -07002243 alternateContentDescription.append(getIntentResolveLabel(alternateIntent, context));
Paul Soulosdd7419d2014-07-15 11:22:13 -07002244 }
2245 }
2246
Paul Souloseb64a4b2014-07-07 17:03:27 -07002247 // If the Entry has no visual elements, return null
2248 if (icon == null && TextUtils.isEmpty(header) && TextUtils.isEmpty(subHeader) &&
2249 subHeaderIcon == null && TextUtils.isEmpty(text) && textIcon == null) {
2250 return null;
2251 }
2252
Brian Attwellfa238942014-08-12 10:21:28 -07002253 // Ignore dataIds from the Me profile.
Paul Soulosea5e0b72014-07-08 18:09:44 -07002254 final int dataId = dataItem.getId() > Integer.MAX_VALUE ?
2255 -1 : (int) dataItem.getId();
2256
Paul Soulos23e28362014-08-29 14:57:08 -07002257 return new Entry(dataId, icon, header, subHeader, subHeaderIcon, text, textIcon,
Tingting Wangb6949dc2015-11-03 13:17:12 -08002258 phoneContentDescription == null
2259 ? new SpannableString(primaryContentDescription.toString())
2260 : phoneContentDescription,
Walter Jang7ce53522014-10-29 13:26:43 -07002261 intent, alternateIcon, alternateIntent,
Wenyi Wang5da55ff2015-11-19 13:22:40 -08002262 smsContentDescription == null
2263 ? new SpannableString(alternateContentDescription.toString())
2264 : smsContentDescription,
2265 shouldApplyColor, isEditable,
Tyler Gunn5f87e922015-08-05 14:24:52 -07002266 entryContextMenuInfo, thirdIcon, thirdIntent, thirdContentDescription, thirdAction,
2267 thirdExtras, iconResourceId);
Paul Souloseb64a4b2014-07-07 17:03:27 -07002268 }
2269
Brian Attwell6bb01342014-08-20 23:16:00 -07002270 private List<Entry> dataItemsToEntries(List<DataItem> dataItems,
2271 MutableString aboutCardTitleOut) {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002272 // Hangouts and G+ use two data items to create one entry.
yaolu3904fb52016-08-05 13:32:56 -07002273 if (dataItems.get(0).getMimeType().equals(MIMETYPE_GPLUS_PROFILE)) {
2274 return gPlusDataItemsToEntries(dataItems);
2275 } else if (dataItems.get(0).getMimeType().equals(MIMETYPE_HANGOUTS)) {
2276 return hangoutsDataItemsToEntries(dataItems);
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002277 } else {
2278 final List<Entry> entries = new ArrayList<>();
2279 for (DataItem dataItem : dataItems) {
2280 final Entry entry = dataItemToEntry(dataItem, /* secondDataItem = */ null,
2281 this, mContactData, aboutCardTitleOut);
2282 if (entry != null) {
2283 entries.add(entry);
2284 }
2285 }
2286 return entries;
2287 }
2288 }
2289
2290 /**
yaolu3904fb52016-08-05 13:32:56 -07002291 * Put the data items into buckets based on the raw contact id
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002292 */
yaolu3904fb52016-08-05 13:32:56 -07002293 private Map<Long, List<DataItem>> dataItemsToBucket(List<DataItem> dataItems) {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002294 final Map<Long, List<DataItem>> buckets = new HashMap<>();
Paul Souloseb64a4b2014-07-07 17:03:27 -07002295 for (DataItem dataItem : dataItems) {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002296 List<DataItem> bucket = buckets.get(dataItem.getRawContactId());
2297 if (bucket == null) {
2298 bucket = new ArrayList<>();
2299 buckets.put(dataItem.getRawContactId(), bucket);
2300 }
2301 bucket.add(dataItem);
2302 }
yaolu3904fb52016-08-05 13:32:56 -07002303 return buckets;
2304 }
2305
2306 /**
2307 * For G+ entries, a single ExpandingEntryCardView.Entry consists of two data items. This
2308 * method use only the View profile to build entry.
2309 */
2310 private List<Entry> gPlusDataItemsToEntries(List<DataItem> dataItems) {
2311 final List<Entry> entries = new ArrayList<>();
2312
2313 for (List<DataItem> bucket : dataItemsToBucket(dataItems).values()) {
2314 for (DataItem dataItem : bucket) {
2315 if (GPLUS_PROFILE_DATA_5_VIEW_PROFILE.equals(
2316 dataItem.getContentValues().getAsString(Data.DATA5))) {
2317 final Entry entry = dataItemToEntry(dataItem, /* secondDataItem = */ null,
2318 this, mContactData, /* aboutCardName = */ null);
2319 if (entry != null) {
2320 entries.add(entry);
2321 }
2322 }
2323 }
2324 }
2325 return entries;
2326 }
2327
2328 /**
2329 * For Hangouts entries, a single ExpandingEntryCardView.Entry consists of two data items. This
2330 * method attempts to build each entry using the two data items if they are available. If there
2331 * are more or less than two data items, a fall back is used and each data item gets its own
2332 * entry.
2333 */
2334 private List<Entry> hangoutsDataItemsToEntries(List<DataItem> dataItems) {
2335 final List<Entry> entries = new ArrayList<>();
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002336
2337 // Use the buckets to build entries. If a bucket contains two data items, build the special
2338 // entry, otherwise fall back to the normal entry.
yaolu3904fb52016-08-05 13:32:56 -07002339 for (List<DataItem> bucket : dataItemsToBucket(dataItems).values()) {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002340 if (bucket.size() == 2) {
2341 // Use the pair to build an entry
2342 final Entry entry = dataItemToEntry(bucket.get(0),
2343 /* secondDataItem = */ bucket.get(1), this, mContactData,
2344 /* aboutCardName = */ null);
2345 if (entry != null) {
2346 entries.add(entry);
2347 }
2348 } else {
2349 for (DataItem dataItem : bucket) {
2350 final Entry entry = dataItemToEntry(dataItem, /* secondDataItem = */ null,
2351 this, mContactData, /* aboutCardName = */ null);
2352 if (entry != null) {
2353 entries.add(entry);
2354 }
2355 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07002356 }
2357 }
2358 return entries;
Daniel Lehmannedb576a2011-07-27 16:45:13 -07002359 }
2360
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002361 /**
yaolu3904fb52016-08-05 13:32:56 -07002362 * Used for statically passing around Hangouts data items and entry fields to
2363 * populateHangoutsDataItemModel.
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002364 */
yaolu3904fb52016-08-05 13:32:56 -07002365 private static final class HangoutsDataItemModel {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002366 public Intent intent;
2367 public Intent alternateIntent;
2368 public DataItem dataItem;
2369 public DataItem secondDataItem;
2370 public StringBuilder alternateContentDescription;
2371 public String header;
2372 public String text;
2373 public Context context;
2374
yaolu3904fb52016-08-05 13:32:56 -07002375 public HangoutsDataItemModel(Intent intent, Intent alternateIntent, DataItem dataItem,
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002376 DataItem secondDataItem, StringBuilder alternateContentDescription, String header,
2377 String text, Context context) {
2378 this.intent = intent;
2379 this.alternateIntent = alternateIntent;
2380 this.dataItem = dataItem;
2381 this.secondDataItem = secondDataItem;
2382 this.alternateContentDescription = alternateContentDescription;
2383 this.header = header;
2384 this.text = text;
2385 this.context = context;
2386 }
2387 }
2388
yaolu3904fb52016-08-05 13:32:56 -07002389 private static void populateHangoutsDataItemModel(
2390 HangoutsDataItemModel dataModel) {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002391 final Intent secondIntent = new Intent(Intent.ACTION_VIEW);
2392 secondIntent.setDataAndType(ContentUris.withAppendedId(Data.CONTENT_URI,
2393 dataModel.secondDataItem.getId()), dataModel.secondDataItem.getMimeType());
yaolu79525d02016-08-24 12:08:39 -07002394 final String secondHeader= dataModel.secondDataItem.buildDataStringForDisplay(
2395 dataModel.context, dataModel.secondDataItem.getDataKind());
2396 secondIntent.putExtra(EXTRA_ACTION_TYPE, ActionType.THIRD_PARTY);
2397 secondIntent.putExtra(EXTRA_THIRD_PARTY_ACTION, secondHeader);
2398
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002399 // There is no guarantee the order the data items come in. Second
2400 // data item does not necessarily mean it's the alternate.
yaolu3904fb52016-08-05 13:32:56 -07002401 // Hangouts video should be alternate. Swap if needed
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002402 if (HANGOUTS_DATA_5_VIDEO.equals(
yaolu3904fb52016-08-05 13:32:56 -07002403 dataModel.dataItem.getContentValues().getAsString(Data.DATA5))) {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002404 dataModel.alternateIntent = dataModel.intent;
2405 dataModel.alternateContentDescription = new StringBuilder(dataModel.header);
2406
2407 dataModel.intent = secondIntent;
yaolu79525d02016-08-24 12:08:39 -07002408 dataModel.header = secondHeader;
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002409 dataModel.text = dataModel.secondDataItem.getDataKind().typeColumn;
yaolu79525d02016-08-24 12:08:39 -07002410
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002411 } else if (HANGOUTS_DATA_5_MESSAGE.equals(
yaolu3904fb52016-08-05 13:32:56 -07002412 dataModel.dataItem.getContentValues().getAsString(Data.DATA5))) {
Paul Soulos6a2a1a72014-10-10 15:52:56 -07002413 dataModel.alternateIntent = secondIntent;
2414 dataModel.alternateContentDescription = new StringBuilder(
2415 dataModel.secondDataItem.buildDataStringForDisplay(dataModel.context,
2416 dataModel.secondDataItem.getDataKind()));
2417 }
2418 }
2419
Brian Attwell6bb01342014-08-20 23:16:00 -07002420 private static String getIntentResolveLabel(Intent intent, Context context) {
2421 final List<ResolveInfo> matches = context.getPackageManager().queryIntentActivities(intent,
Paul Soulosdd7419d2014-07-15 11:22:13 -07002422 PackageManager.MATCH_DEFAULT_ONLY);
2423
2424 // Pick first match, otherwise best found
2425 ResolveInfo bestResolve = null;
2426 final int size = matches.size();
2427 if (size == 1) {
2428 bestResolve = matches.get(0);
2429 } else if (size > 1) {
Brian Attwell6bb01342014-08-20 23:16:00 -07002430 bestResolve = ResolveCache.getInstance(context).getBestResolve(intent, matches);
Paul Soulosdd7419d2014-07-15 11:22:13 -07002431 }
2432
2433 if (bestResolve == null) {
2434 return null;
2435 }
2436
Brian Attwell6bb01342014-08-20 23:16:00 -07002437 return String.valueOf(bestResolve.loadLabel(context.getPackageManager()));
Paul Soulosdd7419d2014-07-15 11:22:13 -07002438 }
2439
Daniel Lehmannedb576a2011-07-27 16:45:13 -07002440 /**
Brian Attwell31b2d422014-06-05 00:14:58 -07002441 * Asynchronously extract the most vibrant color from the PhotoView. Once extracted,
2442 * apply this tint to {@link MultiShrinkScroller}. This operation takes about 20-30ms
2443 * on a Nexus 5.
2444 */
2445 private void extractAndApplyTintFromPhotoViewAsynchronously() {
2446 if (mScroller == null) {
2447 return;
2448 }
2449 final Drawable imageViewDrawable = mPhotoView.getDrawable();
Brian Attwellfaf97392014-07-10 18:32:30 -07002450 new AsyncTask<Void, Void, MaterialPalette>() {
Brian Attwell31b2d422014-06-05 00:14:58 -07002451 @Override
Brian Attwellfaf97392014-07-10 18:32:30 -07002452 protected MaterialPalette doInBackground(Void... params) {
2453
Jay Shrauner929a62f2014-11-13 12:03:50 -08002454 if (imageViewDrawable instanceof BitmapDrawable && mContactData != null
Brian Attwell95c268e2014-08-26 22:04:15 -07002455 && mContactData.getThumbnailPhotoBinaryData() != null
2456 && mContactData.getThumbnailPhotoBinaryData().length > 0) {
2457 // Perform the color analysis on the thumbnail instead of the full sized
2458 // image, so that our results will be as similar as possible to the Bugle
2459 // app.
2460 final Bitmap bitmap = BitmapFactory.decodeByteArray(
2461 mContactData.getThumbnailPhotoBinaryData(), 0,
2462 mContactData.getThumbnailPhotoBinaryData().length);
2463 try {
2464 final int primaryColor = colorFromBitmap(bitmap);
2465 if (primaryColor != 0) {
2466 return mMaterialColorMapUtils.calculatePrimaryAndSecondaryColor(
2467 primaryColor);
2468 }
2469 } finally {
2470 bitmap.recycle();
Brian Attwellfaf97392014-07-10 18:32:30 -07002471 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002472 }
2473 if (imageViewDrawable instanceof LetterTileDrawable) {
Brian Attwellfaf97392014-07-10 18:32:30 -07002474 final int primaryColor = ((LetterTileDrawable) imageViewDrawable).getColor();
Brian Attwella3859ed2014-07-15 13:51:55 -07002475 return mMaterialColorMapUtils.calculatePrimaryAndSecondaryColor(primaryColor);
Brian Attwell31b2d422014-06-05 00:14:58 -07002476 }
Brian Attwella3859ed2014-07-15 13:51:55 -07002477 return MaterialColorMapUtils.getDefaultPrimaryAndSecondaryColors(getResources());
Brian Attwell31b2d422014-06-05 00:14:58 -07002478 }
2479
2480 @Override
Brian Attwellfaf97392014-07-10 18:32:30 -07002481 protected void onPostExecute(MaterialPalette palette) {
2482 super.onPostExecute(palette);
Brian Attwell8571dd32014-06-23 23:29:10 -07002483 if (mHasComputedThemeColor) {
2484 // If we had previously computed a theme color from the contact photo,
2485 // then do not update the theme color. Changing the theme color several
2486 // seconds after QC has started, as a result of an updated/upgraded photo,
2487 // is a jarring experience. On the other hand, changing the theme color after
2488 // a rotation or onNewIntent() is perfectly fine.
2489 return;
2490 }
2491 // Check that the Photo has not changed. If it has changed, the new tint
2492 // color needs to be extracted
2493 if (imageViewDrawable == mPhotoView.getDrawable()) {
2494 mHasComputedThemeColor = true;
Brian Attwellfaf97392014-07-10 18:32:30 -07002495 setThemeColor(palette);
Wenyi Wang1d86a672016-01-28 17:37:10 -08002496 // update color and photo in suggestion card
2497 onAggregationSuggestionChange();
Brian Attwell31b2d422014-06-05 00:14:58 -07002498 }
2499 }
2500 }.execute();
2501 }
2502
Brian Attwellfaf97392014-07-10 18:32:30 -07002503 private void setThemeColor(MaterialPalette palette) {
Brian Attwell9b889e62014-06-23 18:25:32 -07002504 // If the color is invalid, use the predefined default
Brian Attwell8e29faf2015-01-21 10:22:40 -08002505 mColorFilterColor = palette.mPrimaryColor;
2506 mScroller.setHeaderTintColor(mColorFilterColor);
Brian Attwellfaf97392014-07-10 18:32:30 -07002507 mStatusBarColor = palette.mSecondaryColor;
Brian Attwell9b889e62014-06-23 18:25:32 -07002508 updateStatusBarColor();
Brian Attwell8571dd32014-06-23 23:29:10 -07002509
Brian Attwell9b889e62014-06-23 18:25:32 -07002510 mColorFilter =
Brian Attwell8e29faf2015-01-21 10:22:40 -08002511 new PorterDuffColorFilter(mColorFilterColor, PorterDuff.Mode.SRC_ATOP);
2512 mContactCard.setColorAndFilter(mColorFilterColor, mColorFilter);
2513 mRecentCard.setColorAndFilter(mColorFilterColor, mColorFilter);
2514 mAboutCard.setColorAndFilter(mColorFilterColor, mColorFilter);
Gary Maid1d8a7a2016-06-07 14:46:58 -07002515 if (mShouldShowSuggestions) {
2516 mSuggestionsCancelButton.setTextColor(mColorFilterColor);
2517 }
Brian Attwell9b889e62014-06-23 18:25:32 -07002518 }
2519
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002520 private void updateStatusBarColor() {
Wenyi Wang4c3d3e22015-12-17 14:30:02 -08002521 if (mScroller == null || !CompatUtils.isLollipopCompatible()) {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002522 return;
2523 }
2524 final int desiredStatusBarColor;
2525 // Only use a custom status bar color if QuickContacts touches the top of the viewport.
2526 if (mScroller.getScrollNeededToBeFullScreen() <= 0) {
2527 desiredStatusBarColor = mStatusBarColor;
2528 } else {
2529 desiredStatusBarColor = Color.TRANSPARENT;
2530 }
2531 // Animate to the new color.
Brian Attwell847bf2c2014-07-24 13:59:27 -07002532 final ObjectAnimator animation = ObjectAnimator.ofInt(getWindow(), "statusBarColor",
2533 getWindow().getStatusBarColor(), desiredStatusBarColor);
2534 animation.setDuration(ANIMATION_STATUS_BAR_COLOR_CHANGE_DURATION);
2535 animation.setEvaluator(new ArgbEvaluator());
2536 animation.start();
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002537 }
2538
2539 private int colorFromBitmap(Bitmap bitmap) {
2540 // Author of Palette recommends using 24 colors when analyzing profile photos.
2541 final int NUMBER_OF_PALETTE_COLORS = 24;
2542 final Palette palette = Palette.generate(bitmap, NUMBER_OF_PALETTE_COLORS);
Brian Attwella0f20f72014-07-07 11:51:52 -07002543 if (palette != null && palette.getVibrantSwatch() != null) {
2544 return palette.getVibrantSwatch().getRgb();
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002545 }
2546 return 0;
2547 }
2548
Paul Soulosb3054e52014-06-05 16:46:02 -07002549 private List<Entry> contactInteractionsToEntries(List<ContactInteraction> interactions) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07002550 final List<Entry> entries = new ArrayList<>();
Paul Soulosb3054e52014-06-05 16:46:02 -07002551 for (ContactInteraction interaction : interactions) {
Brian Attwelled188282015-02-11 14:12:41 -08002552 if (interaction == null) {
2553 continue;
2554 }
Paul Soulosea5e0b72014-07-08 18:09:44 -07002555 entries.add(new Entry(/* id = */ -1,
2556 interaction.getIcon(this),
Paul Soulosb3054e52014-06-05 16:46:02 -07002557 interaction.getViewHeader(this),
2558 interaction.getViewBody(this),
2559 interaction.getBodyIcon(this),
2560 interaction.getViewFooter(this),
2561 interaction.getFooterIcon(this),
Paul Soulos23e28362014-08-29 14:57:08 -07002562 interaction.getContentDescription(this),
Paul Soulosb3054e52014-06-05 16:46:02 -07002563 interaction.getIntent(),
Paul Soulosdd7419d2014-07-15 11:22:13 -07002564 /* alternateIcon = */ null,
2565 /* alternateIntent = */ null,
2566 /* alternateContentDescription = */ null,
Paul Soulos48ebbaa2014-07-15 13:11:23 -07002567 /* shouldApplyColor = */ true,
Paul Soulos2a4207f2014-07-31 17:09:05 -07002568 /* isEditable = */ false,
Paul Soulos48fc9122014-08-26 13:52:36 -07002569 /* EntryContextMenuInfo = */ null,
2570 /* thirdIcon = */ null,
2571 /* thirdIntent = */ null,
Paul Soulos48290be2014-09-08 13:44:51 -07002572 /* thirdContentDescription = */ null,
Tyler Gunn5f87e922015-08-05 14:24:52 -07002573 /* thirdAction = */ Entry.ACTION_NONE,
2574 /* thirdActionExtras = */ null,
Paul Soulos48290be2014-09-08 13:44:51 -07002575 interaction.getIconResourceId()));
Paul Soulosb3054e52014-06-05 16:46:02 -07002576 }
2577 return entries;
2578 }
2579
Paul Souloseb64a4b2014-07-07 17:03:27 -07002580 private final LoaderCallbacks<Contact> mLoaderContactCallbacks =
Maurice Chu851222a2012-06-21 11:43:08 -07002581 new LoaderCallbacks<Contact>() {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002582 @Override
Maurice Chu851222a2012-06-21 11:43:08 -07002583 public void onLoaderReset(Loader<Contact> loader) {
Paul Soulos405ae402014-07-14 16:16:36 -07002584 mContactData = null;
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002585 }
2586
2587 @Override
Maurice Chu851222a2012-06-21 11:43:08 -07002588 public void onLoadFinished(Loader<Contact> loader, Contact data) {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002589 Trace.beginSection("onLoadFinished()");
Brian Attwell930da3a2014-10-16 21:01:21 -07002590 try {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002591
Brian Attwell930da3a2014-10-16 21:01:21 -07002592 if (isFinishing()) {
2593 return;
2594 }
2595 if (data.isError()) {
Jay Shrauner02ecc3f2014-12-12 11:29:16 -08002596 // This means either the contact is invalid or we had an
2597 // internal error such as an acore crash.
2598 Log.i(TAG, "Failed to load contact: " + ((ContactLoader)loader).getLookupUri());
2599 Toast.makeText(QuickContactActivity.this, R.string.invalidContactMessage,
2600 Toast.LENGTH_LONG).show();
2601 finish();
2602 return;
Brian Attwell930da3a2014-10-16 21:01:21 -07002603 }
2604 if (data.isNotFound()) {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002605 Log.i(TAG, "No contact found: " + ((ContactLoader)loader).getLookupUri());
2606 Toast.makeText(QuickContactActivity.this, R.string.invalidContactMessage,
2607 Toast.LENGTH_LONG).show();
Brian Attwell930da3a2014-10-16 21:01:21 -07002608 finish();
2609 return;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002610 }
Brian Attwell930da3a2014-10-16 21:01:21 -07002611
2612 bindContactData(data);
2613
2614 } finally {
2615 Trace.endSection();
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002616 }
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002617 }
2618
2619 @Override
Maurice Chu851222a2012-06-21 11:43:08 -07002620 public Loader<Contact> onCreateLoader(int id, Bundle args) {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002621 if (mLookupUri == null) {
2622 Log.wtf(TAG, "Lookup uri wasn't initialized. Loader was started too early");
2623 }
Brian Attwelld28851f2014-06-10 13:25:07 -07002624 // Load all contact data. We need loadGroupMetaData=true to determine whether the
2625 // contact is invisible. If it is, we need to display an "Add to Contacts" MenuItem.
Yorke Leeb2b435a2012-11-12 16:47:06 -08002626 return new ContactLoader(getApplicationContext(), mLookupUri,
Brian Attwelld28851f2014-06-10 13:25:07 -07002627 true /*loadGroupMetaData*/, false /*loadInvitableAccountTypes*/,
Brian Attwell8571dd32014-06-23 23:29:10 -07002628 true /*postViewNotification*/, true /*computeFormattedPhoneNumber*/);
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +02002629 }
2630 };
Paul Soulosb3054e52014-06-05 16:46:02 -07002631
Brian Attwellb7e43642014-06-02 14:33:04 -07002632 @Override
2633 public void onBackPressed() {
Tingting Wangac9596e2016-08-02 22:24:24 -07002634 final int previousScreenType = getIntent().getIntExtra
2635 (EXTRA_PREVIOUS_SCREEN_TYPE, ScreenType.UNKNOWN);
2636 if ((previousScreenType == ScreenType.ALL_CONTACTS
2637 || previousScreenType == ScreenType.FAVORITES)
2638 && !SharedPreferenceUtil.getHamburgerPromoTriggerActionHappenedBefore(this)) {
2639 SharedPreferenceUtil.setHamburgerPromoTriggerActionHappenedBefore(this);
2640 }
Brian Attwellb7e43642014-06-02 14:33:04 -07002641 if (mScroller != null) {
Brian Attwell8477eaf2014-06-18 15:39:50 -07002642 if (!mIsExitAnimationInProgress) {
2643 mScroller.scrollOffBottom();
2644 }
Brian Attwellb7e43642014-06-02 14:33:04 -07002645 } else {
2646 super.onBackPressed();
2647 }
2648 }
Paul Soulosb3054e52014-06-05 16:46:02 -07002649
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002650 @Override
2651 public void finish() {
2652 super.finish();
2653
2654 // override transitions to skip the standard window animations
2655 overridePendingTransition(0, 0);
2656 }
2657
Paul Souloseb64a4b2014-07-07 17:03:27 -07002658 private final LoaderCallbacks<List<ContactInteraction>> mLoaderInteractionsCallbacks =
Paul Soulosb3054e52014-06-05 16:46:02 -07002659 new LoaderCallbacks<List<ContactInteraction>>() {
2660
2661 @Override
2662 public Loader<List<ContactInteraction>> onCreateLoader(int id, Bundle args) {
Paul Soulosb3054e52014-06-05 16:46:02 -07002663 Loader<List<ContactInteraction>> loader = null;
2664 switch (id) {
2665 case LOADER_SMS_ID:
Paul Soulosb3054e52014-06-05 16:46:02 -07002666 loader = new SmsInteractionsLoader(
2667 QuickContactActivity.this,
Paul Soulosab840442014-06-17 14:08:40 -07002668 args.getStringArray(KEY_LOADER_EXTRA_PHONES),
Paul Soulosb3054e52014-06-05 16:46:02 -07002669 MAX_SMS_RETRIEVE);
2670 break;
Paul Soulos899aa212014-06-11 12:04:43 -07002671 case LOADER_CALENDAR_ID:
Paul Soulosae4cafe2014-07-09 14:11:03 -07002672 final String[] emailsArray = args.getStringArray(KEY_LOADER_EXTRA_EMAILS);
2673 List<String> emailsList = null;
2674 if (emailsArray != null) {
2675 emailsList = Arrays.asList(args.getStringArray(KEY_LOADER_EXTRA_EMAILS));
2676 }
Paul Soulos899aa212014-06-11 12:04:43 -07002677 loader = new CalendarInteractionsLoader(
2678 QuickContactActivity.this,
Paul Soulosae4cafe2014-07-09 14:11:03 -07002679 emailsList,
Paul Soulos899aa212014-06-11 12:04:43 -07002680 MAX_FUTURE_CALENDAR_RETRIEVE,
2681 MAX_PAST_CALENDAR_RETRIEVE,
2682 FUTURE_MILLISECOND_TO_SEARCH_LOCAL_CALENDAR,
2683 PAST_MILLISECOND_TO_SEARCH_LOCAL_CALENDAR);
2684 break;
Paul Soulosab840442014-06-17 14:08:40 -07002685 case LOADER_CALL_LOG_ID:
Paul Soulosab840442014-06-17 14:08:40 -07002686 loader = new CallLogInteractionsLoader(
2687 QuickContactActivity.this,
2688 args.getStringArray(KEY_LOADER_EXTRA_PHONES),
guanxiongliuc7a4b9c2016-04-30 20:19:21 -07002689 args.getStringArray(KEY_LOADER_EXTRA_SIP_NUMBERS),
Paul Soulosab840442014-06-17 14:08:40 -07002690 MAX_CALL_LOG_RETRIEVE);
Paul Soulosb3054e52014-06-05 16:46:02 -07002691 }
2692 return loader;
2693 }
2694
2695 @Override
2696 public void onLoadFinished(Loader<List<ContactInteraction>> loader,
2697 List<ContactInteraction> data) {
Paul Soulosb3054e52014-06-05 16:46:02 -07002698 mRecentLoaderResults.put(loader.getId(), data);
2699
2700 if (isAllRecentDataLoaded()) {
2701 bindRecentData();
2702 }
2703 }
2704
2705 @Override
2706 public void onLoaderReset(Loader<List<ContactInteraction>> loader) {
2707 mRecentLoaderResults.remove(loader.getId());
2708 }
Paul Soulosb3054e52014-06-05 16:46:02 -07002709 };
2710
2711 private boolean isAllRecentDataLoaded() {
2712 return mRecentLoaderResults.size() == mRecentLoaderIds.length;
2713 }
2714
2715 private void bindRecentData() {
Paul Souloseb64a4b2014-07-07 17:03:27 -07002716 final List<ContactInteraction> allInteractions = new ArrayList<>();
Brian Attwell30e1ef12014-09-02 15:49:20 -07002717 final List<List<Entry>> interactionsWrapper = new ArrayList<>();
Paul Soulosb3054e52014-06-05 16:46:02 -07002718
Brian Attwelled188282015-02-11 14:12:41 -08002719 // Serialize mRecentLoaderResults into a single list. This should be done on the main
2720 // thread to avoid races against mRecentLoaderResults edits.
2721 for (List<ContactInteraction> loaderInteractions : mRecentLoaderResults.values()) {
2722 allInteractions.addAll(loaderInteractions);
2723 }
2724
Brian Attwell30e1ef12014-09-02 15:49:20 -07002725 mRecentDataTask = new AsyncTask<Void, Void, Void>() {
Paul Soulosb3054e52014-06-05 16:46:02 -07002726 @Override
Brian Attwell30e1ef12014-09-02 15:49:20 -07002727 protected Void doInBackground(Void... params) {
2728 Trace.beginSection("sort recent loader results");
Paul Soulosb3054e52014-06-05 16:46:02 -07002729
Brian Attwell30e1ef12014-09-02 15:49:20 -07002730 // Sort the interactions by most recent
2731 Collections.sort(allInteractions, new Comparator<ContactInteraction>() {
2732 @Override
2733 public int compare(ContactInteraction a, ContactInteraction b) {
Brian Attwelled188282015-02-11 14:12:41 -08002734 if (a == null && b == null) {
2735 return 0;
2736 }
2737 if (a == null) {
2738 return 1;
2739 }
2740 if (b == null) {
2741 return -1;
2742 }
2743 if (a.getInteractionDate() > b.getInteractionDate()) {
2744 return -1;
2745 }
2746 if (a.getInteractionDate() == b.getInteractionDate()) {
2747 return 0;
2748 }
2749 return 1;
Brian Attwell30e1ef12014-09-02 15:49:20 -07002750 }
2751 });
2752
2753 Trace.endSection();
2754 Trace.beginSection("contactInteractionsToEntries");
2755
2756 // Wrap each interaction in its own list so that an icon is displayed for each entry
2757 for (Entry contactInteraction : contactInteractionsToEntries(allInteractions)) {
2758 List<Entry> entryListWrapper = new ArrayList<>(1);
2759 entryListWrapper.add(contactInteraction);
2760 interactionsWrapper.add(entryListWrapper);
2761 }
2762
2763 Trace.endSection();
2764 return null;
2765 }
2766
2767 @Override
2768 protected void onPostExecute(Void aVoid) {
2769 super.onPostExecute(aVoid);
2770 Trace.beginSection("initialize recents card");
2771
2772 if (allInteractions.size() > 0) {
2773 mRecentCard.initialize(interactionsWrapper,
Paul Soulosb3054e52014-06-05 16:46:02 -07002774 /* numInitialVisibleEntries = */ MIN_NUM_COLLAPSED_RECENT_ENTRIES_SHOWN,
Paul Soulosc8e2a912014-07-21 14:52:20 -07002775 /* isExpanded = */ mRecentCard.isExpanded(), /* isAlwaysExpanded = */ false,
Brian Attwell30e1ef12014-09-02 15:49:20 -07002776 mExpandingEntryCardViewListener, mScroller);
yaolu79525d02016-08-24 12:08:39 -07002777 if (mRecentCard.getVisibility() == View.GONE && mShouldLog) {
2778 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.RECENT,
2779 ActionType.UNKNOWN_ACTION, /* thirdPartyAction */ null);
2780 }
Brian Attwell30e1ef12014-09-02 15:49:20 -07002781 mRecentCard.setVisibility(View.VISIBLE);
2782 }
Paul Souloseb64a4b2014-07-07 17:03:27 -07002783
Brian Attwell30e1ef12014-09-02 15:49:20 -07002784 Trace.endSection();
guanxiongliu04a19362016-05-18 14:38:51 -07002785 Trace.beginSection("initialize permission explanation card");
2786
guanxiongliu8e56edf2016-07-14 13:48:55 -07002787 final Drawable historyIcon = VectorDrawableCompat.create(
2788 getResources(), R.drawable.ic_history_24dp, null);
2789
guanxiongliu04a19362016-05-18 14:38:51 -07002790 final Entry permissionExplanationEntry = new Entry(CARD_ENTRY_ID_REQUEST_PERMISSION,
2791 historyIcon, getString(R.string.permission_explanation_header),
2792 mPermissionExplanationCardSubHeader, /* subHeaderIcon = */ null,
2793 /* text = */ null, /* textIcon = */ null,
2794 /* primaryContentDescription = */ null, getIntent(),
2795 /* alternateIcon = */ null, /* alternateIntent = */ null,
2796 /* alternateContentDescription = */ null, /* shouldApplyColor = */ true,
2797 /* isEditable = */ false, /* EntryContextMenuInfo = */ null,
2798 /* thirdIcon = */ null, /* thirdIntent = */ null,
2799 /* thirdContentDescription = */ null, /* thirdAction = */ Entry.ACTION_NONE,
2800 /* thirdExtras = */ null, R.drawable.ic_history_24dp);
2801
2802 final List<List<Entry>> permissionExplanationEntries = new ArrayList<>();
2803 permissionExplanationEntries.add(new ArrayList<Entry>());
2804 permissionExplanationEntries.get(0).add(permissionExplanationEntry);
2805
2806 final int subHeaderTextColor = getResources().getColor(android.R.color.white);
2807 final PorterDuffColorFilter whiteColorFilter =
2808 new PorterDuffColorFilter(subHeaderTextColor, PorterDuff.Mode.SRC_ATOP);
2809
2810 mPermissionExplanationCard.initialize(permissionExplanationEntries,
2811 /* numInitialVisibleEntries = */ 1,
2812 /* isExpanded = */ true,
2813 /* isAlwaysExpanded = */ true,
2814 /* listener = */ null,
2815 mScroller);
2816
2817 mPermissionExplanationCard.setColorAndFilter(subHeaderTextColor, whiteColorFilter);
2818 mPermissionExplanationCard.setBackgroundColor(mColorFilterColor);
2819 mPermissionExplanationCard.setEntryHeaderColor(subHeaderTextColor);
2820 mPermissionExplanationCard.setEntrySubHeaderColor(subHeaderTextColor);
2821
2822 if (mShouldShowPermissionExplanation) {
yaolu79525d02016-08-24 12:08:39 -07002823 if (mPermissionExplanationCard.getVisibility() == View.GONE
2824 && mShouldLog) {
2825 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.PERMISSION,
2826 ActionType.UNKNOWN_ACTION, /* thirdPartyAction */ null);
2827 }
guanxiongliu04a19362016-05-18 14:38:51 -07002828 mPermissionExplanationCard.setVisibility(View.VISIBLE);
2829 } else {
2830 mPermissionExplanationCard.setVisibility(View.GONE);
2831 }
2832
2833 Trace.endSection();
Brian Attwell30e1ef12014-09-02 15:49:20 -07002834
2835 // About card is initialized along with the contact card, but since it appears after
2836 // the recent card in the UI, we hold off until making it visible until the recent
2837 // card is also ready to avoid stuttering.
2838 if (mAboutCard.shouldShow()) {
2839 mAboutCard.setVisibility(View.VISIBLE);
2840 } else {
2841 mAboutCard.setVisibility(View.GONE);
2842 }
2843 mRecentDataTask = null;
2844 }
2845 };
2846 mRecentDataTask.execute();
Paul Soulosb3054e52014-06-05 16:46:02 -07002847 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002848
2849 @Override
2850 protected void onStop() {
2851 super.onStop();
2852
2853 if (mEntriesAndActionsTask != null) {
2854 // Once the activity is stopped, we will no longer want to bind mEntriesAndActionsTask's
2855 // results on the UI thread. In some circumstances Activities are killed without
2856 // onStop() being called. This is not a problem, because in these circumstances
2857 // the entire process will be killed.
2858 mEntriesAndActionsTask.cancel(/* mayInterruptIfRunning = */ false);
2859 }
Brian Attwell30e1ef12014-09-02 15:49:20 -07002860 if (mRecentDataTask != null) {
2861 mRecentDataTask.cancel(/* mayInterruptIfRunning = */ false);
2862 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -07002863 }
Paul Soulos23889ba2014-06-10 14:55:32 -07002864
Tingting Wange3a7c4f2015-09-24 18:23:18 -07002865 @Override
2866 public void onDestroy() {
2867 super.onDestroy();
2868 if (mAggregationSuggestionEngine != null) {
2869 mAggregationSuggestionEngine.quit();
2870 }
2871 }
2872
Paul Soulos23889ba2014-06-10 14:55:32 -07002873 /**
Brian Attwelld28851f2014-06-10 13:25:07 -07002874 * Returns true if it is possible to edit the current contact.
2875 */
2876 private boolean isContactEditable() {
2877 return mContactData != null && !mContactData.isDirectoryEntry();
2878 }
2879
Brian Attwella41d6d12014-07-31 15:23:13 -07002880 /**
2881 * Returns true if it is possible to share the current contact.
2882 */
2883 private boolean isContactShareable() {
2884 return mContactData != null && !mContactData.isDirectoryEntry();
2885 }
2886
Brian Attwell60953692014-07-11 17:18:46 -07002887 private Intent getEditContactIntent() {
John Shao41c68862016-08-17 21:02:41 -07002888 return EditorIntents.createCompactEditContactIntent(QuickContactActivity.this,
Walter Jang1e8801b2015-03-10 15:57:05 -07002889 mContactData.getLookupUri(),
2890 mHasComputedThemeColor
2891 ? new MaterialPalette(mColorFilterColor, mStatusBarColor) : null,
Wenyi Wangc41a1e52016-01-29 17:01:35 +00002892 mContactData.getPhotoId());
Brian Attwell60953692014-07-11 17:18:46 -07002893 }
2894
2895 private void editContact() {
Paul Soulosa42ef762014-08-20 10:26:10 -07002896 mHasIntentLaunched = true;
Zheng Fuc00a0b52014-09-02 16:44:44 -07002897 mContactLoader.cacheResult();
Brian Attwell60953692014-07-11 17:18:46 -07002898 startActivityForResult(getEditContactIntent(), REQUEST_CODE_CONTACT_EDITOR_ACTIVITY);
Brian Attwelld28851f2014-06-10 13:25:07 -07002899 }
2900
Walter Jang5d2e2622014-11-03 13:20:48 -08002901 private void deleteContact() {
2902 final Uri contactUri = mContactData.getLookupUri();
2903 ContactDeletionInteraction.start(this, contactUri, /* finishActivityWhenDone =*/ true);
2904 }
2905
yaolu79525d02016-08-24 12:08:39 -07002906 private void toggleStar(MenuItem starredMenuItem, boolean isStarred) {
2907 // To improve responsiveness, swap out the picture (and tag) in the UI already
2908 ContactDisplayUtils.configureStarredMenuItem(starredMenuItem,
2909 mContactData.isDirectoryEntry(), mContactData.isUserProfile(), !isStarred);
Brian Attwelld28851f2014-06-10 13:25:07 -07002910
yaolu79525d02016-08-24 12:08:39 -07002911 // Now perform the real save
2912 final Intent intent = ContactSaveService.createSetStarredIntent(
2913 QuickContactActivity.this, mContactData.getLookupUri(), !isStarred);
2914 startService(intent);
Brian Attwelld28851f2014-06-10 13:25:07 -07002915
yaolu79525d02016-08-24 12:08:39 -07002916 final CharSequence accessibilityText = !isStarred
2917 ? getResources().getText(R.string.description_action_menu_add_star)
2918 : getResources().getText(R.string.description_action_menu_remove_star);
2919 // Accessibility actions need to have an associated view. We can't access the MenuItem's
2920 // underlying view, so put this accessibility action on the root view.
2921 mScroller.announceForAccessibility(accessibilityText);
Brian Attwelld28851f2014-06-10 13:25:07 -07002922 }
2923
Brian Attwell752cccf2014-06-10 16:24:04 -07002924 private void shareContact() {
2925 final String lookupKey = mContactData.getLookupKey();
Brian Attwell652936f2015-05-27 19:40:36 -07002926 final Uri shareUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey);
Brian Attwell752cccf2014-06-10 16:24:04 -07002927 final Intent intent = new Intent(Intent.ACTION_SEND);
Yorke Lee552ee562015-07-28 15:58:56 -07002928 intent.setType(Contacts.CONTENT_VCARD_TYPE);
Brian Attwell8dcb4ed2015-06-15 15:45:03 -07002929 intent.putExtra(Intent.EXTRA_STREAM, shareUri);
Brian Attwell752cccf2014-06-10 16:24:04 -07002930
2931 // Launch chooser to share contact via
Wenyi Wang281689f2016-05-30 10:32:30 -07002932 final CharSequence chooseTitle = getResources().getQuantityString(
2933 R.plurals.title_share_via, /* quantity */ 1);
Brian Attwell752cccf2014-06-10 16:24:04 -07002934 final Intent chooseIntent = Intent.createChooser(intent, chooseTitle);
2935
2936 try {
Paul Soulosa42ef762014-08-20 10:26:10 -07002937 mHasIntentLaunched = true;
Brian Attwell652936f2015-05-27 19:40:36 -07002938 ImplicitIntentsUtil.startActivityOutsideApp(this, chooseIntent);
Paul Souloseb64a4b2014-07-07 17:03:27 -07002939 } catch (final ActivityNotFoundException ex) {
Brian Attwell752cccf2014-06-10 16:24:04 -07002940 Toast.makeText(this, R.string.share_error, Toast.LENGTH_SHORT).show();
2941 }
2942 }
2943
2944 /**
2945 * Creates a launcher shortcut with the current contact.
2946 */
2947 private void createLauncherShortcutWithContact() {
2948 final ShortcutIntentBuilder builder = new ShortcutIntentBuilder(this,
2949 new OnShortcutIntentCreatedListener() {
2950
2951 @Override
2952 public void onShortcutIntentCreated(Uri uri, Intent shortcutIntent) {
2953 // Broadcast the shortcutIntent to the launcher to create a
2954 // shortcut to this contact
2955 shortcutIntent.setAction(ACTION_INSTALL_SHORTCUT);
2956 QuickContactActivity.this.sendBroadcast(shortcutIntent);
2957
2958 // Send a toast to give feedback to the user that a shortcut to this
2959 // contact was added to the launcher.
guanxiongliuce8fc7b2016-03-28 11:32:32 -07002960 final String displayName = shortcutIntent
2961 .getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
Tingting Wang168331d2015-10-30 12:00:57 -07002962 final String toastMessage = TextUtils.isEmpty(displayName)
2963 ? getString(R.string.createContactShortcutSuccessful_NoName)
2964 : getString(R.string.createContactShortcutSuccessful, displayName);
2965 Toast.makeText(QuickContactActivity.this, toastMessage,
Brian Attwell752cccf2014-06-10 16:24:04 -07002966 Toast.LENGTH_SHORT).show();
2967 }
2968
2969 });
Brian Attwell63176c92014-08-18 15:14:18 -07002970 builder.createContactShortcutIntent(mContactData.getLookupUri());
Brian Attwell752cccf2014-06-10 16:24:04 -07002971 }
2972
Brian Attwell66965e12014-09-08 16:15:20 -07002973 private boolean isShortcutCreatable() {
Jay Shrauner1c06ce72015-05-01 11:56:08 -07002974 if (mContactData == null || mContactData.isUserProfile() ||
2975 mContactData.isDirectoryEntry()) {
Brian Attwell8d0557e2014-12-02 11:18:16 -08002976 return false;
2977 }
Brian Attwell66965e12014-09-08 16:15:20 -07002978 final Intent createShortcutIntent = new Intent();
2979 createShortcutIntent.setAction(ACTION_INSTALL_SHORTCUT);
2980 final List<ResolveInfo> receivers = getPackageManager()
2981 .queryBroadcastReceivers(createShortcutIntent, 0);
2982 return receivers != null && receivers.size() > 0;
2983 }
2984
Brian Attwelld28851f2014-06-10 13:25:07 -07002985 @Override
2986 public boolean onCreateOptionsMenu(Menu menu) {
Paul Souloseb64a4b2014-07-07 17:03:27 -07002987 final MenuInflater inflater = getMenuInflater();
Brian Attwelld28851f2014-06-10 13:25:07 -07002988 inflater.inflate(R.menu.quickcontact, menu);
2989 return true;
2990 }
2991
2992 @Override
2993 public boolean onPrepareOptionsMenu(Menu menu) {
2994 if (mContactData != null) {
2995 final MenuItem starredMenuItem = menu.findItem(R.id.menu_star);
Paul Soulos333091a2014-07-22 13:54:41 -07002996 ContactDisplayUtils.configureStarredMenuItem(starredMenuItem,
Brian Attwelld28851f2014-06-10 13:25:07 -07002997 mContactData.isDirectoryEntry(), mContactData.isUserProfile(),
2998 mContactData.getStarred());
Brian Attwella41d6d12014-07-31 15:23:13 -07002999
Brian Attwelld28851f2014-06-10 13:25:07 -07003000 // Configure edit MenuItem
3001 final MenuItem editMenuItem = menu.findItem(R.id.menu_edit);
3002 editMenuItem.setVisible(true);
3003 if (DirectoryContactUtil.isDirectoryContact(mContactData) || InvisibleContactUtil
3004 .isInvisibleAndAddable(mContactData, this)) {
Brian Attwell30cfd122014-06-13 16:42:11 -07003005 editMenuItem.setIcon(R.drawable.ic_person_add_tinted_24dp);
Brian Attwell2e4214c2014-07-10 22:03:16 -07003006 editMenuItem.setTitle(R.string.menu_add_contact);
Brian Attwelld28851f2014-06-10 13:25:07 -07003007 } else if (isContactEditable()) {
3008 editMenuItem.setIcon(R.drawable.ic_create_24dp);
Brian Attwell2e4214c2014-07-10 22:03:16 -07003009 editMenuItem.setTitle(R.string.menu_editContact);
Brian Attwelld28851f2014-06-10 13:25:07 -07003010 } else {
3011 editMenuItem.setVisible(false);
3012 }
Brian Attwella41d6d12014-07-31 15:23:13 -07003013
Walter Jang5d2e2622014-11-03 13:20:48 -08003014 final MenuItem deleteMenuItem = menu.findItem(R.id.menu_delete);
Walter Jang82ed2b52015-09-09 12:01:04 -07003015 deleteMenuItem.setVisible(isContactEditable() && !mContactData.isUserProfile());
Walter Jang5d2e2622014-11-03 13:20:48 -08003016
Brian Attwella41d6d12014-07-31 15:23:13 -07003017 final MenuItem shareMenuItem = menu.findItem(R.id.menu_share);
3018 shareMenuItem.setVisible(isContactShareable());
3019
Brian Attwell66965e12014-09-08 16:15:20 -07003020 final MenuItem shortcutMenuItem = menu.findItem(R.id.menu_create_contact_shortcut);
3021 shortcutMenuItem.setVisible(isShortcutCreatable());
3022
Brian Attwell56bcc2f2015-02-12 23:45:27 +00003023 final MenuItem helpMenu = menu.findItem(R.id.menu_help);
3024 helpMenu.setVisible(HelpUtils.isHelpAndFeedbackAvailable());
3025
Paul Soulos8a6d0022014-06-16 15:11:03 -07003026 return true;
Brian Attwelld28851f2014-06-10 13:25:07 -07003027 }
Paul Soulos8a6d0022014-06-16 15:11:03 -07003028 return false;
Brian Attwelld28851f2014-06-10 13:25:07 -07003029 }
3030
3031 @Override
3032 public boolean onOptionsItemSelected(MenuItem item) {
3033 switch (item.getItemId()) {
3034 case R.id.menu_star:
yaolu79525d02016-08-24 12:08:39 -07003035 // Make sure there is a contact
3036 if (mContactData != null) {
3037 // Read the current starred value from the UI instead of using the last
3038 // loaded state. This allows rapid tapping without writing the same
3039 // value several times
3040 final boolean isStarred = item.isChecked();
3041 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
3042 isStarred ? ActionType.UNSTAR : ActionType.STAR,
3043 /* thirdPartyAction */ null);
3044 toggleStar(item, isStarred);
3045 }
Brian Attwelld28851f2014-06-10 13:25:07 -07003046 return true;
3047 case R.id.menu_edit:
3048 if (DirectoryContactUtil.isDirectoryContact(mContactData)) {
yaolu79525d02016-08-24 12:08:39 -07003049 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
3050 ActionType.ADD, /* thirdPartyAction */ null);
3051
Paul Soulosab54ea12014-08-28 17:22:38 -07003052 // This action is used to launch the contact selector, with the option of
3053 // creating a new contact. Creating a new contact is an INSERT, while selecting
3054 // an exisiting one is an edit. The fields in the edit screen will be
3055 // prepopulated with data.
3056
3057 final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
3058 intent.setType(Contacts.CONTENT_ITEM_TYPE);
3059
Paul Soulosab54ea12014-08-28 17:22:38 -07003060 ArrayList<ContentValues> values = mContactData.getContentValues();
Brian Attwellfc423b42014-10-17 12:58:15 -07003061
3062 // Only pre-fill the name field if the provided display name is an nickname
3063 // or better (e.g. structured name, nickname)
3064 if (mContactData.getDisplayNameSource() >= DisplayNameSources.NICKNAME) {
3065 intent.putExtra(Intents.Insert.NAME, mContactData.getDisplayName());
3066 } else if (mContactData.getDisplayNameSource()
3067 == DisplayNameSources.ORGANIZATION) {
3068 // This is probably an organization. Instead of copying the organization
3069 // name into a name entry, copy it into the organization entry. This
3070 // way we will still consider the contact an organization.
3071 final ContentValues organization = new ContentValues();
3072 organization.put(Organization.COMPANY, mContactData.getDisplayName());
3073 organization.put(Data.MIMETYPE, Organization.CONTENT_ITEM_TYPE);
3074 values.add(organization);
3075 }
3076
Paul Soulosab54ea12014-08-28 17:22:38 -07003077 // Last time used and times used are aggregated values from the usage stat
3078 // table. They need to be removed from data values so the SQL table can insert
3079 // properly
3080 for (ContentValues value : values) {
3081 value.remove(Data.LAST_TIME_USED);
3082 value.remove(Data.TIMES_USED);
3083 }
3084 intent.putExtra(Intents.Insert.DATA, values);
3085
3086 // If the contact can only export to the same account, add it to the intent.
3087 // Otherwise the ContactEditorFragment will show a dialog for selecting an
3088 // account.
3089 if (mContactData.getDirectoryExportSupport() ==
3090 Directory.EXPORT_SUPPORT_SAME_ACCOUNT_ONLY) {
Brian Attwell4a1c5742015-01-26 15:59:58 -08003091 intent.putExtra(Intents.Insert.EXTRA_ACCOUNT,
Paul Soulosab54ea12014-08-28 17:22:38 -07003092 new Account(mContactData.getDirectoryAccountName(),
3093 mContactData.getDirectoryAccountType()));
Brian Attwell4a1c5742015-01-26 15:59:58 -08003094 intent.putExtra(Intents.Insert.EXTRA_DATA_SET,
Paul Soulosab54ea12014-08-28 17:22:38 -07003095 mContactData.getRawContacts().get(0).getDataSet());
3096 }
3097
Paul Soulosf19dda92014-09-15 15:48:02 -07003098 // Add this flag to disable the delete menu option on directory contact joins
3099 // with local contacts. The delete option is ambiguous when joining contacts.
3100 intent.putExtra(ContactEditorFragment.INTENT_EXTRA_DISABLE_DELETE_MENU_OPTION,
3101 true);
3102
John Shao41c68862016-08-17 21:02:41 -07003103 intent.setPackage(getPackageName());
Paul Soulosab54ea12014-08-28 17:22:38 -07003104 startActivityForResult(intent, REQUEST_CODE_CONTACT_SELECTION_ACTIVITY);
Brian Attwelld28851f2014-06-10 13:25:07 -07003105 } else if (InvisibleContactUtil.isInvisibleAndAddable(mContactData, this)) {
yaolu79525d02016-08-24 12:08:39 -07003106 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
3107 ActionType.ADD, /* thirdPartyAction */ null);
Brian Attwelld28851f2014-06-10 13:25:07 -07003108 InvisibleContactUtil.addToDefaultGroup(mContactData, this);
3109 } else if (isContactEditable()) {
yaolu79525d02016-08-24 12:08:39 -07003110 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
3111 ActionType.EDIT, /* thirdPartyAction */ null);
Brian Attwelld28851f2014-06-10 13:25:07 -07003112 editContact();
3113 }
3114 return true;
Walter Jang5d2e2622014-11-03 13:20:48 -08003115 case R.id.menu_delete:
yaolu79525d02016-08-24 12:08:39 -07003116 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
3117 ActionType.DELETE, /* thirdPartyAction */ null);
Jay Shrauner1c06ce72015-05-01 11:56:08 -07003118 if (isContactEditable()) {
3119 deleteContact();
3120 }
Walter Jang5d2e2622014-11-03 13:20:48 -08003121 return true;
Brian Attwell752cccf2014-06-10 16:24:04 -07003122 case R.id.menu_share:
yaolu79525d02016-08-24 12:08:39 -07003123 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
3124 ActionType.SHARE, /* thirdPartyAction */ null);
Jay Shrauner49de62f2014-11-26 10:44:28 -08003125 if (isContactShareable()) {
3126 shareContact();
3127 }
Brian Attwell752cccf2014-06-10 16:24:04 -07003128 return true;
3129 case R.id.menu_create_contact_shortcut:
yaolu79525d02016-08-24 12:08:39 -07003130 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
3131 ActionType.SHORTCUT, /* thirdPartyAction */ null);
Jay Shrauner1c06ce72015-05-01 11:56:08 -07003132 if (isShortcutCreatable()) {
3133 createLauncherShortcutWithContact();
3134 }
Brian Attwell752cccf2014-06-10 16:24:04 -07003135 return true;
Brian Attwell56bcc2f2015-02-12 23:45:27 +00003136 case R.id.menu_help:
yaolu79525d02016-08-24 12:08:39 -07003137 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
3138 ActionType.HELP, /* thirdPartyAction */ null);
Brian Attwell56bcc2f2015-02-12 23:45:27 +00003139 HelpUtils.launchHelpAndFeedbackForContactScreen(this);
3140 return true;
Brian Attwelld28851f2014-06-10 13:25:07 -07003141 default:
yaolu79525d02016-08-24 12:08:39 -07003142 Logger.logQuickContactEvent(mReferrer, mContactType, CardType.UNKNOWN_CARD,
3143 ActionType.UNKNOWN_ACTION, /* thirdPartyAction */ null);
Brian Attwelld28851f2014-06-10 13:25:07 -07003144 return super.onOptionsItemSelected(item);
3145 }
Paul Soulos23889ba2014-06-10 14:55:32 -07003146 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -07003147}