blob: 8341c7ffd50c3fa601d0b3d809daa72f7c6a56e0 [file] [log] [blame]
Daniel Lehmannedb576a2011-07-27 16:45:13 -07001/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.contacts.quickcontact;
18
Brian Attwell8a6f4ad2014-06-06 21:54:53 -070019import android.animation.ArgbEvaluator;
Brian Attwellb7e43642014-06-02 14:33:04 -070020import android.animation.ObjectAnimator;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070021import android.app.Activity;
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +020022import android.app.LoaderManager.LoaderCallbacks;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070023import android.content.ContentUris;
24import android.content.Context;
25import android.content.Intent;
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +020026import android.content.Loader;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070027import android.content.pm.PackageManager;
Brian Attwell31b2d422014-06-05 00:14:58 -070028import android.graphics.Bitmap;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -070029import android.graphics.Color;
Brian Attwell31b2d422014-06-05 00:14:58 -070030import android.graphics.drawable.BitmapDrawable;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -070031import android.graphics.drawable.ColorDrawable;
Brian Attwell31b2d422014-06-05 00:14:58 -070032import android.graphics.drawable.Drawable;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070033import android.net.Uri;
Brian Attwell31b2d422014-06-05 00:14:58 -070034import android.os.AsyncTask;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070035import android.os.Bundle;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -070036import android.os.Trace;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070037import android.provider.ContactsContract.CommonDataKinds.Email;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070038import android.provider.ContactsContract.CommonDataKinds.Phone;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070039import android.provider.ContactsContract.CommonDataKinds.SipAddress;
40import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
41import android.provider.ContactsContract.CommonDataKinds.Website;
42import android.provider.ContactsContract.Contacts;
Yorke Lee6cb6f6f2013-09-26 16:26:03 -070043import android.provider.ContactsContract.DisplayNameSources;
44import android.provider.ContactsContract.Intents.Insert;
45import android.provider.ContactsContract.Directory;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070046import android.provider.ContactsContract.QuickContact;
47import android.provider.ContactsContract.RawContacts;
Brian Attwell31b2d422014-06-05 00:14:58 -070048import android.support.v7.graphics.Palette;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070049import android.text.TextUtils;
50import android.util.Log;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070051import android.view.View;
52import android.view.View.OnClickListener;
Daisuke Miyakawa10d7df72011-07-29 16:11:05 -070053import android.view.WindowManager;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070054import android.widget.ImageView;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070055import android.widget.TextView;
56import android.widget.Toast;
57
Christine Chen72b3ab12013-08-13 23:22:34 +020058import com.android.contacts.ContactSaveService;
Chiao Chengd7ca03e2012-10-24 15:14:08 -070059import com.android.contacts.common.Collapser;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070060import com.android.contacts.R;
Brian Attwell31b2d422014-06-05 00:14:58 -070061import com.android.contacts.common.lettertiles.LetterTileDrawable;
Chiao Cheng0d5588d2012-11-26 15:34:14 -080062import com.android.contacts.common.model.AccountTypeManager;
Yorke Leecd321f62013-10-28 15:20:15 -070063import com.android.contacts.common.model.Contact;
64import com.android.contacts.common.model.ContactLoader;
65import com.android.contacts.common.model.RawContact;
Chiao Cheng428f0082012-11-13 18:38:56 -080066import com.android.contacts.common.model.account.AccountType;
Yorke Leecd321f62013-10-28 15:20:15 -070067import com.android.contacts.common.model.dataitem.DataItem;
Chiao Cheng428f0082012-11-13 18:38:56 -080068import com.android.contacts.common.model.dataitem.DataKind;
Yorke Leecd321f62013-10-28 15:20:15 -070069import com.android.contacts.common.model.dataitem.EmailDataItem;
70import com.android.contacts.common.model.dataitem.ImDataItem;
Paul Soulosb3054e52014-06-05 16:46:02 -070071import com.android.contacts.common.model.dataitem.PhoneDataItem;
Yorke Leecd321f62013-10-28 15:20:15 -070072import com.android.contacts.common.util.DataStatus;
Yorke Lee2e385832013-09-09 14:42:01 -070073import com.android.contacts.common.util.UriUtils;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -070074import com.android.contacts.interactions.ContactDeletionInteraction;
Paul Soulosb3054e52014-06-05 16:46:02 -070075import com.android.contacts.interactions.ContactInteraction;
Paul Soulosb3054e52014-06-05 16:46:02 -070076import com.android.contacts.interactions.SmsInteractionsLoader;
Paul Soulos2d48b5a2014-05-29 13:56:25 -070077import com.android.contacts.quickcontact.ExpandingEntryCardView.Entry;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070078import com.android.contacts.util.ImageViewDrawableSetter;
Brian Attwellb7e43642014-06-02 14:33:04 -070079import com.android.contacts.util.SchedulingUtils;
80import com.android.contacts.widget.MultiShrinkScroller;
81import com.android.contacts.widget.MultiShrinkScroller.MultiShrinkScrollerListener;
Paul Soulos2d48b5a2014-05-29 13:56:25 -070082
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070083import com.google.common.base.Preconditions;
84import com.google.common.collect.Lists;
85
Paul Soulos2d48b5a2014-05-29 13:56:25 -070086import java.util.ArrayList;
Paul Soulosb3054e52014-06-05 16:46:02 -070087import java.util.Collections;
88import java.util.Comparator;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070089import java.util.HashMap;
90import java.util.HashSet;
91import java.util.List;
Paul Soulosb3054e52014-06-05 16:46:02 -070092import java.util.Map;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070093import java.util.Set;
94
Daniel Lehmannedb576a2011-07-27 16:45:13 -070095/**
96 * Mostly translucent {@link Activity} that shows QuickContact dialog. It loads
97 * data asynchronously, and then shows a popup with details centered around
98 * {@link Intent#getSourceBounds()}.
99 */
100public class QuickContactActivity extends Activity {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700101
102 /**
103 * QuickContacts immediately takes up the full screen. All possible information is shown.
104 * This value for {@link android.provider.ContactsContract.QuickContact#EXTRA_MODE}
105 * should only be used by the Contacts app.
106 */
107 public static final int MODE_FULLY_EXPANDED = 4;
108
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700109 private static final String TAG = "QuickContact";
110
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700111 private static final int ANIMATION_SLIDE_OPEN_DURATION = 250;
112 private static final int ANIMATION_STATUS_BAR_COLOR_CHANGE_DURATION = 75;
113 private static final int REQUEST_CODE_CONTACT_EDITOR_ACTIVITY = 1;
114 private static final float SYSTEM_BAR_BRIGHTNESS_FACTOR = 0.7f;
115 private static final int SHIM_COLOR = Color.argb(0x7F, 0, 0, 0);
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700116
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700117
118 @SuppressWarnings("deprecation")
119 private static final String LEGACY_AUTHORITY = android.provider.Contacts.AUTHORITY;
120
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700121 private Uri mLookupUri;
122 private String[] mExcludeMimes;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700123 private int mExtraMode;
124 private int mStatusBarColor;
125 private boolean mHasAlreadyBeenOpened;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700126
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700127 private View mPhotoContainer;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700128
Yorke Lee62196682013-09-06 18:34:17 -0700129 private ImageView mPhotoView;
Paul Soulos5ec23fd2014-05-29 18:19:38 -0700130 private ImageView mEditOrAddContactImage;
Christine Chen72b3ab12013-08-13 23:22:34 +0200131 private ImageView mStarImage;
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700132 private ExpandingEntryCardView mCommunicationCard;
Paul Soulosb3054e52014-06-05 16:46:02 -0700133 private ExpandingEntryCardView mRecentCard;
Brian Attwellb7e43642014-06-02 14:33:04 -0700134 private MultiShrinkScroller mScroller;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700135 private AsyncTask<Void, Void, Void> mEntriesAndActionsTask;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700136
Paul Soulosb3054e52014-06-05 16:46:02 -0700137 private static final int MIN_NUM_COMMUNICATION_ENTRIES_SHOWN = 3;
138 private static final int MIN_NUM_COLLAPSED_RECENT_ENTRIES_SHOWN = 3;
139
Yorke Lee6cb6f6f2013-09-26 16:26:03 -0700140 private Contact mContactData;
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700141 private ContactLoader mContactLoader;
142
Josh Gargus9758a922012-03-08 17:12:42 -0800143 private final ImageViewDrawableSetter mPhotoSetter = new ImageViewDrawableSetter();
144
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700145 /**
146 * Keeps the default action per mimetype. Empty if no default actions are set
147 */
148 private HashMap<String, Action> mDefaultsMap = new HashMap<String, Action>();
149
150 /**
151 * Set of {@link Action} that are associated with the aggregate currently
152 * displayed by this dialog, represented as a map from {@link String}
153 * MIME-type to a list of {@link Action}.
154 */
155 private ActionMultiMap mActions = new ActionMultiMap();
156
157 /**
158 * {@link #LEADING_MIMETYPES} and {@link #TRAILING_MIMETYPES} are used to sort MIME-types.
159 *
160 * <p>The MIME-types in {@link #LEADING_MIMETYPES} appear in the front of the dialog,
161 * in the order specified here.</p>
162 *
163 * <p>The ones in {@link #TRAILING_MIMETYPES} appear in the end of the dialog, in the order
164 * specified here.</p>
165 *
166 * <p>The rest go between them, in the order in the array.</p>
167 */
168 private static final List<String> LEADING_MIMETYPES = Lists.newArrayList(
169 Phone.CONTENT_ITEM_TYPE, SipAddress.CONTENT_ITEM_TYPE, Email.CONTENT_ITEM_TYPE);
170
171 /** See {@link #LEADING_MIMETYPES}. */
172 private static final List<String> TRAILING_MIMETYPES = Lists.newArrayList(
173 StructuredPostal.CONTENT_ITEM_TYPE, Website.CONTENT_ITEM_TYPE);
174
Paul Soulosb3054e52014-06-05 16:46:02 -0700175 /** Id for the background contact loader */
176 private static final int LOADER_CONTACT_ID = 0;
177
178 /** Id for the background Sms Loader */
179 private static final int LOADER_SMS_ID = 1;
180 private static final String KEY_LOADER_EXTRA_SMS_PHONES =
181 QuickContactActivity.class.getCanonicalName() + ".KEY_LOADER_EXTRA_SMS_PHONES";
182 private static final int MAX_SMS_RETRIEVE = 3;
183
184 private static final int[] mRecentLoaderIds = new int[LOADER_SMS_ID];
185 private Map<Integer, List<ContactInteraction>> mRecentLoaderResults;
186
Paul Soulos5ec23fd2014-05-29 18:19:38 -0700187 final OnClickListener mEditContactClickHandler = new OnClickListener() {
Yorke Lee6cb6f6f2013-09-26 16:26:03 -0700188 @Override
189 public void onClick(View v) {
Paul Soulos5ec23fd2014-05-29 18:19:38 -0700190 final Intent intent = new Intent(Intent.ACTION_EDIT, mLookupUri);
Yorke Lee6cb6f6f2013-09-26 16:26:03 -0700191 mContactLoader.cacheResult();
192 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700193 startActivityForResult(intent, REQUEST_CODE_CONTACT_EDITOR_ACTIVITY);
Yorke Lee6cb6f6f2013-09-26 16:26:03 -0700194 }
195 };
196
197 final OnClickListener mAddToContactsClickHandler = new OnClickListener() {
198 @Override
199 public void onClick(View v) {
200 if (mContactData == null) {
201 Log.e(TAG, "Empty contact data when trying to add to contact");
202 return;
203 }
204 final Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
205 intent.setType(Contacts.CONTENT_ITEM_TYPE);
206
207 // Only pre-fill the name field if the provided display name is an organization
208 // name or better (e.g. structured name, nickname)
209 if (mContactData.getDisplayNameSource() >= DisplayNameSources.ORGANIZATION) {
210 intent.putExtra(Insert.NAME, mContactData.getDisplayName());
211 }
212 intent.putExtra(Insert.DATA, mContactData.getContentValues());
213 startActivity(intent);
214 }
215 };
216
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700217 final OnClickListener mEntryClickHandler = new OnClickListener() {
218 @Override
219 public void onClick(View v) {
220 Log.i(TAG, "mEntryClickHandler onClick");
221 Object intent = v.getTag();
222 if (intent == null || !(intent instanceof Intent)) {
223 return;
224 }
225 startActivity((Intent) intent);
226 }
227 };
228
Brian Attwellb7e43642014-06-02 14:33:04 -0700229 final MultiShrinkScrollerListener mMultiShrinkScrollerListener
230 = new MultiShrinkScrollerListener() {
231 @Override
232 public void onScrolledOffBottom() {
233 onBackPressed();
234 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700235
236 @Override
237 public void onEnterFullscreen() {
238 updateStatusBarColor();
239 }
240
241 @Override
242 public void onExitFullscreen() {
243 updateStatusBarColor();
244 }
Brian Attwellb7e43642014-06-02 14:33:04 -0700245 };
246
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700247 @Override
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700248 protected void onCreate(Bundle savedInstanceState) {
249 Trace.beginSection("onCreate()");
250 super.onCreate(savedInstanceState);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700251
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700252 getWindow().setStatusBarColor(Color.TRANSPARENT);
253 // Since we can't disable Window animations from the Launcher, we can minimize the
254 // silliness of the animation by setting the navigation bar transparent.
255 getWindow().setNavigationBarColor(Color.TRANSPARENT);
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700256
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700257 // Parse intent
258 final Intent intent = getIntent();
259
260 Uri lookupUri = intent.getData();
261
262 // Check to see whether it comes from the old version.
263 if (lookupUri != null && LEGACY_AUTHORITY.equals(lookupUri.getAuthority())) {
264 final long rawContactId = ContentUris.parseId(lookupUri);
265 lookupUri = RawContacts.getContactLookupUri(getContentResolver(),
266 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
267 }
268
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700269 mExtraMode = getIntent().getIntExtra(QuickContact.EXTRA_MODE,
270 QuickContact.MODE_LARGE);
271
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700272 mLookupUri = Preconditions.checkNotNull(lookupUri, "missing lookupUri");
273
274 mExcludeMimes = intent.getStringArrayExtra(QuickContact.EXTRA_EXCLUDE_MIMES);
275
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700276 mContactLoader = (ContactLoader) getLoaderManager().initLoader(
Paul Soulosb3054e52014-06-05 16:46:02 -0700277 LOADER_CONTACT_ID, null, mLoaderContactCallbacks);
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700278
Daisuke Miyakawa10d7df72011-07-29 16:11:05 -0700279 // Show QuickContact in front of soft input
280 getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
281 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
282
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700283 setContentView(R.layout.quickcontact_activity);
284
Paul Soulos5ec23fd2014-05-29 18:19:38 -0700285 mEditOrAddContactImage = (ImageView) findViewById(R.id.contact_edit_image);
Christine Chen72b3ab12013-08-13 23:22:34 +0200286 mStarImage = (ImageView) findViewById(R.id.quickcontact_star_button);
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700287 mCommunicationCard = (ExpandingEntryCardView) findViewById(R.id.communication_card);
Paul Soulosb3054e52014-06-05 16:46:02 -0700288 mRecentCard = (ExpandingEntryCardView) findViewById(R.id.recent_card);
Brian Attwellb7e43642014-06-02 14:33:04 -0700289 mScroller = (MultiShrinkScroller) findViewById(R.id.multiscroller);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700290
Paul Soulos5ec23fd2014-05-29 18:19:38 -0700291 mEditOrAddContactImage.setOnClickListener(mEditContactClickHandler);
Paul Soulosb3054e52014-06-05 16:46:02 -0700292
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700293 mCommunicationCard.setOnClickListener(mEntryClickHandler);
Paul Soulosb3054e52014-06-05 16:46:02 -0700294 mCommunicationCard.setTitle(getResources().getString(R.string.communication_card_title));
295
296 mRecentCard.setOnClickListener(mEntryClickHandler);
297 mRecentCard.setTitle(getResources().getString(R.string.recent_card_title));
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700298
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700299 // find and prepare correct header view
300 mPhotoContainer = findViewById(R.id.photo_container);
Yorke Lee62196682013-09-06 18:34:17 -0700301
Katherine Kuan81281ee2011-07-28 16:20:59 -0700302 setHeaderNameText(R.id.name, R.string.missing_name);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700303
Yorke Lee62196682013-09-06 18:34:17 -0700304 mPhotoView = (ImageView) mPhotoContainer.findViewById(R.id.photo);
Paul Soulos5ec23fd2014-05-29 18:19:38 -0700305 mPhotoView.setOnClickListener(mEditContactClickHandler);
Yorke Lee62196682013-09-06 18:34:17 -0700306
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700307 mHasAlreadyBeenOpened = savedInstanceState != null;
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700308
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700309 final ColorDrawable windowShim = new ColorDrawable(SHIM_COLOR);
310 getWindow().setBackgroundDrawable(windowShim);
311 if (!mHasAlreadyBeenOpened) {
312 final int duration = getResources().getInteger(android.R.integer.config_shortAnimTime);
313 ObjectAnimator.ofInt(windowShim, "alpha", 0, 0xFF).setDuration(duration).start();
Brian Attwellb7e43642014-06-02 14:33:04 -0700314 }
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700315
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700316 if (mScroller != null) {
317 mScroller.initialize(mMultiShrinkScrollerListener);
318 if (mHasAlreadyBeenOpened) {
319 mScroller.setVisibility(View.VISIBLE);
320 mScroller.setScroll(mScroller.getScrollNeededToBeFullScreen());
321 } else {
322 mScroller.setVisibility(View.GONE);
323 }
324 }
325
326 Trace.endSection();
327 }
328
329 protected void onActivityResult(int requestCode, int resultCode,
330 Intent data) {
331 if (requestCode == REQUEST_CODE_CONTACT_EDITOR_ACTIVITY &&
332 resultCode == ContactDeletionInteraction.RESULT_CODE_DELETED) {
333 // The contact that we were showing has been deleted.
334 finish();
335 }
336 }
337
338 @Override
339 protected void onNewIntent(Intent intent) {
340 super.onNewIntent(intent);
341 mHasAlreadyBeenOpened = true;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700342 }
343
Brian Attwellb7e43642014-06-02 14:33:04 -0700344 private void runEntranceAnimation() {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700345 if (mHasAlreadyBeenOpened) {
346 return;
347 }
348 mHasAlreadyBeenOpened = true;
Brian Attwellb7e43642014-06-02 14:33:04 -0700349 final int bottomScroll = mScroller.getScrollUntilOffBottom() - 1;
350 final ObjectAnimator scrollAnimation
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700351 = ObjectAnimator.ofInt(mScroller, "scroll", -bottomScroll,
352 mExtraMode != MODE_FULLY_EXPANDED ? 0 : mScroller.getScrollNeededToBeFullScreen());
353 scrollAnimation.setDuration(ANIMATION_SLIDE_OPEN_DURATION);
Brian Attwellb7e43642014-06-02 14:33:04 -0700354 scrollAnimation.start();
355 }
356
Katherine Kuan81281ee2011-07-28 16:20:59 -0700357 /** Assign this string to the view if it is not empty. */
358 private void setHeaderNameText(int id, int resId) {
359 setHeaderNameText(id, getText(resId));
360 }
361
362 /** Assign this string to the view if it is not empty. */
363 private void setHeaderNameText(int id, CharSequence value) {
364 final View view = mPhotoContainer.findViewById(id);
365 if (view instanceof TextView) {
366 if (!TextUtils.isEmpty(value)) {
367 ((TextView)view).setText(value);
368 }
369 }
370 }
371
372 /**
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700373 * Check if the given MIME-type appears in the list of excluded MIME-types
374 * that the most-recent caller requested.
375 */
376 private boolean isMimeExcluded(String mimeType) {
377 if (mExcludeMimes == null) return false;
378 for (String excludedMime : mExcludeMimes) {
379 if (TextUtils.equals(excludedMime, mimeType)) {
380 return true;
381 }
382 }
383 return false;
384 }
385
386 /**
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200387 * Handle the result from the ContactLoader
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700388 */
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700389 private void bindContactData(final Contact data) {
390 Trace.beginSection("bindContactData");
Yorke Lee6cb6f6f2013-09-26 16:26:03 -0700391 mContactData = data;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700392 final Context context = this;
393
Paul Soulos5ec23fd2014-05-29 18:19:38 -0700394 mEditOrAddContactImage.setVisibility(isMimeExcluded(Contacts.CONTENT_ITEM_TYPE) ?
Yorke Lee6cb6f6f2013-09-26 16:26:03 -0700395 View.GONE : View.VISIBLE);
Christine Chen72b3ab12013-08-13 23:22:34 +0200396 final boolean isStarred = data.getStarred();
397 if (isStarred) {
398 mStarImage.setImageResource(R.drawable.ic_favorite_on_lt);
Ben Gilad69f67d02013-10-04 15:39:40 -0700399 mStarImage.setContentDescription(
400 getResources().getString(R.string.menu_removeStar));
Christine Chen72b3ab12013-08-13 23:22:34 +0200401 } else {
402 mStarImage.setImageResource(R.drawable.ic_favorite_off_lt);
Ben Gilad69f67d02013-10-04 15:39:40 -0700403 mStarImage.setContentDescription(
404 getResources().getString(R.string.menu_addStar));
Christine Chen72b3ab12013-08-13 23:22:34 +0200405 }
406 final Uri lookupUri = data.getLookupUri();
Christine Chen72b3ab12013-08-13 23:22:34 +0200407
Yorke Lee2e385832013-09-09 14:42:01 -0700408 // If this is a json encoded URI, there is no local contact to star
409 if (UriUtils.isEncodedContactUri(lookupUri)) {
410 mStarImage.setVisibility(View.GONE);
Yorke Lee6cb6f6f2013-09-26 16:26:03 -0700411
412 // If directory export support is not allowed, then don't allow the user to add
413 // to contacts
414 if (mContactData.getDirectoryExportSupport() == Directory.EXPORT_SUPPORT_NONE) {
Yorke Lee88df7122013-09-29 15:00:18 -0700415 configureHeaderClickActions(false);
Yorke Lee6cb6f6f2013-09-26 16:26:03 -0700416 } else {
Yorke Lee88df7122013-09-29 15:00:18 -0700417 configureHeaderClickActions(true);
Yorke Lee6cb6f6f2013-09-26 16:26:03 -0700418 }
Yorke Lee2e385832013-09-09 14:42:01 -0700419 } else {
Yorke Lee88df7122013-09-29 15:00:18 -0700420 configureHeaderClickActions(false);
Yorke Lee2e385832013-09-09 14:42:01 -0700421 mStarImage.setVisibility(View.VISIBLE);
422 mStarImage.setOnClickListener(new OnClickListener() {
423 @Override
424 public void onClick(View view) {
425 // Toggle "starred" state
426 // Make sure there is a contact
427 if (lookupUri != null) {
428 // Changes the state of the image already before sending updates to the
429 // database
430 if (isStarred) {
431 mStarImage.setImageResource(R.drawable.ic_favorite_off_lt);
432 } else {
433 mStarImage.setImageResource(R.drawable.ic_favorite_on_lt);
434 }
435
436 // Now perform the real save
437 final Intent intent = ContactSaveService.createSetStarredIntent(context,
438 lookupUri, !isStarred);
439 context.startService(intent);
440 }
Christine Chen72b3ab12013-08-13 23:22:34 +0200441 }
Yorke Lee2e385832013-09-09 14:42:01 -0700442 });
443 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700444
445 mDefaultsMap.clear();
446
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700447 Trace.endSection();
448 Trace.beginSection("Set display photo & name");
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700449
Yorke Lee62196682013-09-06 18:34:17 -0700450 mPhotoSetter.setupContactPhoto(data, mPhotoView);
Brian Attwell31b2d422014-06-05 00:14:58 -0700451 extractAndApplyTintFromPhotoViewAsynchronously();
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700452 setHeaderNameText(R.id.name, data.getDisplayName());
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700453
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700454 Trace.endSection();
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700455
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700456 final List<String> sortedActionMimeTypes = Lists.newArrayList();
Paul Soulosb3054e52014-06-05 16:46:02 -0700457 // Maintain a list of phone numbers to pass into SmsInteractionsLoader
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700458 final List<String> phoneNumbers = Lists.newArrayList();
459 // List of Entry that makes up the ExpandingEntryCardView
460 final List<Entry> entries = Lists.newArrayList();
461
462 mEntriesAndActionsTask = new AsyncTask<Void, Void, Void>() {
463 @Override
464 protected Void doInBackground(Void... params) {
465 computeEntriesAndActions(data, phoneNumbers, sortedActionMimeTypes, entries);
466 return null;
467 }
468
469 @Override
470 protected void onPostExecute(Void aVoid) {
471 super.onPostExecute(aVoid);
472 // Check that original AsyncTask parameters are still valid and the activity
473 // is still running before binding to UI. A new intent could invalidate
474 // the results, for example.
475 if (data == mContactData && !isCancelled()) {
476 bindEntriesAndActions(entries, phoneNumbers, sortedActionMimeTypes);
477 showActivity();
478 }
479 }
480 };
481 mEntriesAndActionsTask.execute();
482 }
483
484 private void bindEntriesAndActions(List<Entry> entries,
485 List<String> phoneNumbers,
486 List<String> sortedActionMimeTypes) {
487 Trace.beginSection("start sms loader");
488
489 Bundle smsExtraBundle = new Bundle();
490 smsExtraBundle.putStringArray(KEY_LOADER_EXTRA_SMS_PHONES,
491 phoneNumbers.toArray(new String[phoneNumbers.size()]));
492 getLoaderManager().initLoader(
493 LOADER_SMS_ID,
494 smsExtraBundle,
495 mLoaderInteractionsCallbacks);
496
497 Trace.endSection();
498 Trace.beginSection("bind communicate card");
499
500 if (entries.size() > 0) {
501 mCommunicationCard.initialize(entries,
502 /* numInitialVisibleEntries = */ MIN_NUM_COMMUNICATION_ENTRIES_SHOWN,
503 /* isExpanded = */ false,
504 /* themeColor = */ 0);
505 }
506
507 final boolean hasData = !sortedActionMimeTypes.isEmpty();
508 mCommunicationCard.setVisibility(hasData ? View.VISIBLE: View.GONE);
509
510 Trace.endSection();
511 }
512
513 private void showActivity() {
514 if (mScroller != null) {
515 mScroller.setVisibility(View.VISIBLE);
516 SchedulingUtils.doOnPreDraw(mScroller, /* drawNextFrame = */ false,
517 new Runnable() {
518 @Override
519 public void run() {
520 runEntranceAnimation();
521 }
522 });
523 }
524 }
525
526 private void computeEntriesAndActions(Contact data, List<String> phoneNumbers,
527 List<String> sortedActionMimeTypes, List<Entry> entries) {
528 Trace.beginSection("inflate entries and actions");
529
530 final ResolveCache cache = ResolveCache.getInstance(this);
Maurice Chu851222a2012-06-21 11:43:08 -0700531 for (RawContact rawContact : data.getRawContacts()) {
532 for (DataItem dataItem : rawContact.getDataItems()) {
533 final String mimeType = dataItem.getMimeType();
Chiao Cheng47b6f702012-09-07 17:28:17 -0700534 final AccountType accountType = rawContact.getAccountType(this);
535 final DataKind dataKind = AccountTypeManager.getInstance(this)
536 .getKindOrFallback(accountType, mimeType);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700537
Paul Soulosb3054e52014-06-05 16:46:02 -0700538 if (dataItem instanceof PhoneDataItem) {
539 phoneNumbers.add(((PhoneDataItem) dataItem).getNormalizedNumber());
540 }
541
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200542 // Skip this data item if MIME-type excluded
543 if (isMimeExcluded(mimeType)) continue;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700544
Maurice Chu851222a2012-06-21 11:43:08 -0700545 final long dataId = dataItem.getId();
546 final boolean isPrimary = dataItem.isPrimary();
547 final boolean isSuperPrimary = dataItem.isSuperPrimary();
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700548
Chiao Cheng47b6f702012-09-07 17:28:17 -0700549 if (dataKind != null) {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200550 // Build an action for this data entry, find a mapping to a UI
551 // element, build its summary from the cursor, and collect it
552 // along with all others of this MIME-type.
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700553 final Action action = new DataAction(getApplicationContext(),
554 dataItem, dataKind);
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700555 final boolean wasAdded = considerAdd(action, cache, isSuperPrimary);
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200556 if (wasAdded) {
557 // Remember the default
558 if (isSuperPrimary || (isPrimary && (mDefaultsMap.get(mimeType) == null))) {
559 mDefaultsMap.put(mimeType, action);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700560 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700561 }
562 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700563
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200564 // Handle Email rows with presence data as Im entry
Daniel Lehmann9daca142011-12-20 11:41:22 +0200565 final DataStatus status = data.getStatuses().get(dataId);
Maurice Chu851222a2012-06-21 11:43:08 -0700566 if (status != null && dataItem instanceof EmailDataItem) {
567 final EmailDataItem email = (EmailDataItem) dataItem;
568 final ImDataItem im = ImDataItem.createFromEmail(email);
Chiao Cheng47b6f702012-09-07 17:28:17 -0700569 if (dataKind != null) {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700570 final DataAction action = new DataAction(getApplicationContext(),
571 im, dataKind);
Daniel Lehmann9daca142011-12-20 11:41:22 +0200572 action.setPresence(status.getPresence());
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700573 considerAdd(action, cache, isSuperPrimary);
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200574 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700575 }
576 }
577 }
578
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700579 Trace.endSection();
580 Trace.beginSection("collapsing action list");
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700581
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700582 // Collapse Action Lists (remove e.g. duplicate e-mail addresses from different sources)
583 for (List<Action> actionChildren : mActions.values()) {
584 Collapser.collapseList(actionChildren);
585 }
586
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700587 Trace.endSection();
588 Trace.beginSection("sort mimetypes");
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700589
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700590 // All the mime-types to add.
591 final Set<String> containedTypes = new HashSet<String>(mActions.keySet());
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700592 // First, add LEADING_MIMETYPES, which are most common.
593 for (String mimeType : LEADING_MIMETYPES) {
594 if (containedTypes.contains(mimeType)) {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700595 sortedActionMimeTypes.add(mimeType);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700596 containedTypes.remove(mimeType);
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700597 entries.addAll(actionsToEntries(mActions.get(mimeType)));
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700598 }
599 }
600
601 // Add all the remaining ones that are not TRAILING
602 for (String mimeType : containedTypes.toArray(new String[containedTypes.size()])) {
603 if (!TRAILING_MIMETYPES.contains(mimeType)) {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700604 sortedActionMimeTypes.add(mimeType);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700605 containedTypes.remove(mimeType);
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700606 entries.addAll(actionsToEntries(mActions.get(mimeType)));
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700607 }
608 }
609
610 // Then, add TRAILING_MIMETYPES, which are least common.
611 for (String mimeType : TRAILING_MIMETYPES) {
612 if (containedTypes.contains(mimeType)) {
613 containedTypes.remove(mimeType);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700614 sortedActionMimeTypes.add(mimeType);
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700615 entries.addAll(actionsToEntries(mActions.get(mimeType)));
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700616 }
617 }
Paul Soulosb3054e52014-06-05 16:46:02 -0700618
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700619 Trace.endSection();
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700620 }
621
622 /**
Brian Attwell31b2d422014-06-05 00:14:58 -0700623 * Asynchronously extract the most vibrant color from the PhotoView. Once extracted,
624 * apply this tint to {@link MultiShrinkScroller}. This operation takes about 20-30ms
625 * on a Nexus 5.
626 */
627 private void extractAndApplyTintFromPhotoViewAsynchronously() {
628 if (mScroller == null) {
629 return;
630 }
631 final Drawable imageViewDrawable = mPhotoView.getDrawable();
632 new AsyncTask<Void, Void, Integer>() {
633 @Override
634 protected Integer doInBackground(Void... params) {
Brian Attwell31b2d422014-06-05 00:14:58 -0700635 if (imageViewDrawable instanceof BitmapDrawable) {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700636 final Bitmap bitmap = ((BitmapDrawable) imageViewDrawable).getBitmap();
637 return colorFromBitmap(bitmap);
638 }
639 if (imageViewDrawable instanceof LetterTileDrawable) {
Brian Attwell31b2d422014-06-05 00:14:58 -0700640 // LetterTileDrawable doesn't normally draw unless it is visible. Therefore,
641 // we need to directly ask it for its color via getColor(). We could directly
642 // return this color. However, in the future Palette#generate() may incorporate
643 // saturation boosting. So I want to use Palette#generate() for the sake of
644 // consistency.
645 final LetterTileDrawable tileDrawable = (LetterTileDrawable) imageViewDrawable;
646 final int PALETTE_BITMAP_SIZE = 1;
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700647 final Bitmap bitmap = Bitmap.createBitmap(PALETTE_BITMAP_SIZE,
Brian Attwell31b2d422014-06-05 00:14:58 -0700648 PALETTE_BITMAP_SIZE, Bitmap.Config.ARGB_8888);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700649 // If Palette can not extract a primary color, our UX person says we are better
650 // off using the LetterTileDrawable's non vibrant color than falling back
651 // to the app's default color.
652 final int color = colorFromBitmap(bitmap);
653 if (color == 0) {
654 return tileDrawable.getColor();
655 } else {
656 return color;
657 }
Brian Attwell31b2d422014-06-05 00:14:58 -0700658 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700659 return 0;
Brian Attwell31b2d422014-06-05 00:14:58 -0700660 }
661
662 @Override
663 protected void onPostExecute(Integer color) {
664 super.onPostExecute(color);
665 if (color != 0 && imageViewDrawable == mPhotoView.getDrawable()) {
666 // TODO: animate from the previous tint.
667 mScroller.setHeaderTintColor(color);
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700668
669 // Create a darker version of the actionbar color. HSV is device dependent
670 // and not perceptually-linear. Therefore, we can't say mStatusBarColor is
671 // 70% as bright as the action bar color. We can only say: it is a bit darker.
672 final float hsvComponents[] = new float[3];
673 Color.colorToHSV(color, hsvComponents);
674 hsvComponents[2] *= SYSTEM_BAR_BRIGHTNESS_FACTOR;
675 mStatusBarColor = Color.HSVToColor(hsvComponents);
676
677 updateStatusBarColor();
Brian Attwell31b2d422014-06-05 00:14:58 -0700678 }
679 }
680 }.execute();
681 }
682
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700683 private void updateStatusBarColor() {
684 if (mScroller == null) {
685 return;
686 }
687 final int desiredStatusBarColor;
688 // Only use a custom status bar color if QuickContacts touches the top of the viewport.
689 if (mScroller.getScrollNeededToBeFullScreen() <= 0) {
690 desiredStatusBarColor = mStatusBarColor;
691 } else {
692 desiredStatusBarColor = Color.TRANSPARENT;
693 }
694 // Animate to the new color.
695 if (desiredStatusBarColor != getWindow().getStatusBarColor()) {
696 final ObjectAnimator animation = ObjectAnimator.ofInt(getWindow(), "statusBarColor",
697 getWindow().getStatusBarColor(), desiredStatusBarColor);
698 animation.setDuration(ANIMATION_STATUS_BAR_COLOR_CHANGE_DURATION);
699 animation.setEvaluator(new ArgbEvaluator());
700 animation.start();
701 }
702 }
703
704 private int colorFromBitmap(Bitmap bitmap) {
705 // Author of Palette recommends using 24 colors when analyzing profile photos.
706 final int NUMBER_OF_PALETTE_COLORS = 24;
707 final Palette palette = Palette.generate(bitmap, NUMBER_OF_PALETTE_COLORS);
708 if (palette != null && palette.getVibrantColor() != null) {
709 return palette.getVibrantColor().getRgb();
710 }
711 return 0;
712 }
713
Brian Attwell31b2d422014-06-05 00:14:58 -0700714 /**
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700715 * Consider adding the given {@link Action}, which will only happen if
716 * {@link PackageManager} finds an application to handle
717 * {@link Action#getIntent()}.
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700718 * @param action the action to handle
719 * @param resolveCache cache of applications that can handle actions
720 * @param front indicates whether to add the action to the front of the list
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700721 * @return true if action has been added
722 */
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700723 private boolean considerAdd(Action action, ResolveCache resolveCache, boolean front) {
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700724 if (resolveCache.hasResolve(action)) {
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700725 mActions.put(action.getMimeType(), action, front);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700726 return true;
727 }
728 return false;
729 }
730
731 /**
Yorke Lee88df7122013-09-29 15:00:18 -0700732 * Bind the correct image resource and click handlers to the header views
733 *
734 * @param canAdd Whether or not the user can directly add information in this quick contact
735 * to their local contacts
736 */
737 private void configureHeaderClickActions(boolean canAdd) {
738 if (canAdd) {
Paul Soulosb8eb33c2014-05-30 15:32:48 -0700739 mEditOrAddContactImage.setImageResource(R.drawable.ic_person_add_24dp);
Paul Soulos5ec23fd2014-05-29 18:19:38 -0700740 mEditOrAddContactImage.setOnClickListener(mAddToContactsClickHandler);
Yorke Lee88df7122013-09-29 15:00:18 -0700741 mPhotoView.setOnClickListener(mAddToContactsClickHandler);
742 } else {
Paul Soulosb8eb33c2014-05-30 15:32:48 -0700743 mEditOrAddContactImage.setImageResource(R.drawable.ic_create_24dp);
Paul Soulos5ec23fd2014-05-29 18:19:38 -0700744 mEditOrAddContactImage.setOnClickListener(mEditContactClickHandler);
745 mPhotoView.setOnClickListener(mEditContactClickHandler);
Yorke Lee88df7122013-09-29 15:00:18 -0700746 }
747 }
748
749 /**
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700750 * Converts a list of Action into a list of Entry
751 * @param actions The list of Action to convert
752 * @return The converted list of Entry
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700753 */
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700754 private List<Entry> actionsToEntries(List<Action> actions) {
755 List<Entry> entries = new ArrayList<>();
756 for (Action action : actions) {
757 entries.add(new Entry(ResolveCache.getInstance(this).getIcon(action),
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700758 action.getMimeType(),
759 action.getSubtitle() == null ? null : action.getSubtitle().toString(),
760 action.getBody() == null ? null : action.getBody().toString(),
761 action.getIntent(), /* isEditable= */ false));
Paul Soulos2d48b5a2014-05-29 13:56:25 -0700762 }
763 return entries;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700764 }
765
Paul Soulosb3054e52014-06-05 16:46:02 -0700766 private List<Entry> contactInteractionsToEntries(List<ContactInteraction> interactions) {
767 List<Entry> entries = new ArrayList<>();
768 for (ContactInteraction interaction : interactions) {
769 entries.add(new Entry(interaction.getIcon(this),
770 interaction.getViewHeader(this),
771 interaction.getViewBody(this),
772 interaction.getBodyIcon(this),
773 interaction.getViewFooter(this),
774 interaction.getFooterIcon(this),
775 interaction.getIntent(),
776 /* isEditable = */ false));
777 }
778 return entries;
779 }
780
781 private LoaderCallbacks<Contact> mLoaderContactCallbacks =
Maurice Chu851222a2012-06-21 11:43:08 -0700782 new LoaderCallbacks<Contact>() {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200783 @Override
Maurice Chu851222a2012-06-21 11:43:08 -0700784 public void onLoaderReset(Loader<Contact> loader) {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200785 }
786
787 @Override
Maurice Chu851222a2012-06-21 11:43:08 -0700788 public void onLoadFinished(Loader<Contact> loader, Contact data) {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700789 Trace.beginSection("onLoadFinished()");
790
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200791 if (isFinishing()) {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200792 return;
793 }
794 if (data.isError()) {
795 // This shouldn't ever happen, so throw an exception. The {@link ContactLoader}
796 // should log the actual exception.
797 throw new IllegalStateException("Failed to load contact", data.getException());
798 }
799 if (data.isNotFound()) {
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700800 if (mHasAlreadyBeenOpened) {
801 finish();
802 } else {
803 Log.i(TAG, "No contact found: " + ((ContactLoader)loader).getLookupUri());
804 Toast.makeText(QuickContactActivity.this, R.string.invalidContactMessage,
805 Toast.LENGTH_LONG).show();
806 }
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200807 return;
808 }
809
Paul Soulosb3054e52014-06-05 16:46:02 -0700810 bindContactData(data);
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200811
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700812 Trace.endSection();
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200813 }
814
815 @Override
Maurice Chu851222a2012-06-21 11:43:08 -0700816 public Loader<Contact> onCreateLoader(int id, Bundle args) {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200817 if (mLookupUri == null) {
818 Log.wtf(TAG, "Lookup uri wasn't initialized. Loader was started too early");
819 }
Yorke Leeb2b435a2012-11-12 16:47:06 -0800820 return new ContactLoader(getApplicationContext(), mLookupUri,
Paul Sliwowski45f23d42013-08-27 15:29:40 -0700821 false /*loadGroupMetaData*/, false /*loadInvitableAccountTypes*/,
822 false /*postViewNotification*/, true /*computeFormattedPhoneNumber*/);
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200823 }
824 };
Paul Soulosb3054e52014-06-05 16:46:02 -0700825
Brian Attwellb7e43642014-06-02 14:33:04 -0700826 @Override
827 public void onBackPressed() {
828 if (mScroller != null) {
829 // TODO: implement exit animation if the scroller isn't already off the screen
830 finish();
831 } else {
832 super.onBackPressed();
833 }
834 }
Paul Soulosb3054e52014-06-05 16:46:02 -0700835
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700836 @Override
837 public void finish() {
838 super.finish();
839
840 // override transitions to skip the standard window animations
841 overridePendingTransition(0, 0);
842 }
843
Paul Soulosb3054e52014-06-05 16:46:02 -0700844 private LoaderCallbacks<List<ContactInteraction>> mLoaderInteractionsCallbacks =
845 new LoaderCallbacks<List<ContactInteraction>>() {
846
847 @Override
848 public Loader<List<ContactInteraction>> onCreateLoader(int id, Bundle args) {
849 Log.v(TAG, "onCreateLoader");
850 Loader<List<ContactInteraction>> loader = null;
851 switch (id) {
852 case LOADER_SMS_ID:
853 Log.v(TAG, "LOADER_SMS_ID");
854 loader = new SmsInteractionsLoader(
855 QuickContactActivity.this,
856 args.getStringArray(KEY_LOADER_EXTRA_SMS_PHONES),
857 MAX_SMS_RETRIEVE);
858 break;
859 }
860 return loader;
861 }
862
863 @Override
864 public void onLoadFinished(Loader<List<ContactInteraction>> loader,
865 List<ContactInteraction> data) {
866 if (mRecentLoaderResults == null) {
867 mRecentLoaderResults = new HashMap<Integer, List<ContactInteraction>>();
868 }
869 mRecentLoaderResults.put(loader.getId(), data);
870
871 if (isAllRecentDataLoaded()) {
872 bindRecentData();
873 }
874 }
875
876 @Override
877 public void onLoaderReset(Loader<List<ContactInteraction>> loader) {
878 mRecentLoaderResults.remove(loader.getId());
879 }
880
881 };
882
883 private boolean isAllRecentDataLoaded() {
884 return mRecentLoaderResults.size() == mRecentLoaderIds.length;
885 }
886
887 private void bindRecentData() {
888 List<ContactInteraction> allInteractions = new ArrayList<>();
889 for (List<ContactInteraction> loaderInteractions : mRecentLoaderResults.values()) {
890 allInteractions.addAll(loaderInteractions);
891 }
892
893 // Sort the interactions by most recent
894 Collections.sort(allInteractions, new Comparator<ContactInteraction>() {
895 @Override
896 public int compare(ContactInteraction a, ContactInteraction b) {
897 return a.getInteractionDate() >= b.getInteractionDate() ? -1 : 1;
898 }
899 });
900
901 if (allInteractions.size() > 0) {
902 mRecentCard.initialize(contactInteractionsToEntries(allInteractions),
903 /* numInitialVisibleEntries = */ MIN_NUM_COLLAPSED_RECENT_ENTRIES_SHOWN,
904 /* isExpanded = */ false,
905 /* themeColor = */ 0);
906 mRecentCard.setVisibility(View.VISIBLE);
907 }
908 }
Brian Attwell8a6f4ad2014-06-06 21:54:53 -0700909
910 @Override
911 protected void onStop() {
912 super.onStop();
913
914 if (mEntriesAndActionsTask != null) {
915 // Once the activity is stopped, we will no longer want to bind mEntriesAndActionsTask's
916 // results on the UI thread. In some circumstances Activities are killed without
917 // onStop() being called. This is not a problem, because in these circumstances
918 // the entire process will be killed.
919 mEntriesAndActionsTask.cancel(/* mayInterruptIfRunning = */ false);
920 }
921 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700922}