blob: 1e91fb638627c527bfbe7c2423f585273fc106ba [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
Daniel Lehmannedb576a2011-07-27 16:45:13 -070019import android.app.Activity;
20import android.app.Fragment;
21import android.app.FragmentManager;
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +020022import android.app.LoaderManager.LoaderCallbacks;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070023import android.content.ActivityNotFoundException;
24import android.content.ContentUris;
25import android.content.Context;
26import android.content.Intent;
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +020027import android.content.Loader;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070028import android.content.pm.PackageManager;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070029import android.graphics.Rect;
30import android.graphics.drawable.Drawable;
31import android.net.Uri;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070032import android.os.Bundle;
33import android.os.Handler;
34import android.provider.ContactsContract.CommonDataKinds.Email;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070035import android.provider.ContactsContract.CommonDataKinds.Phone;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070036import android.provider.ContactsContract.CommonDataKinds.SipAddress;
37import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
38import android.provider.ContactsContract.CommonDataKinds.Website;
39import android.provider.ContactsContract.Contacts;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070040import android.provider.ContactsContract.QuickContact;
41import android.provider.ContactsContract.RawContacts;
42import android.support.v13.app.FragmentPagerAdapter;
Chiao Cheng35ad0c32013-02-12 12:25:45 -080043import android.support.v4.view.PagerAdapter;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070044import android.support.v4.view.ViewPager;
45import android.support.v4.view.ViewPager.SimpleOnPageChangeListener;
46import android.text.TextUtils;
47import android.util.Log;
48import android.view.MotionEvent;
49import android.view.View;
50import android.view.View.OnClickListener;
51import android.view.ViewGroup;
Daisuke Miyakawa10d7df72011-07-29 16:11:05 -070052import android.view.WindowManager;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070053import android.widget.HorizontalScrollView;
Daniel Lehmannedb576a2011-07-27 16:45:13 -070054import android.widget.ImageView;
55import android.widget.RelativeLayout;
56import android.widget.TextView;
57import android.widget.Toast;
58
Christine Chen72b3ab12013-08-13 23:22:34 +020059import com.android.contacts.ContactSaveService;
Chiao Chengd7ca03e2012-10-24 15:14:08 -070060import com.android.contacts.common.Collapser;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070061import com.android.contacts.R;
Chiao Cheng0d5588d2012-11-26 15:34:14 -080062import com.android.contacts.common.model.AccountTypeManager;
Maurice Chu851222a2012-06-21 11:43:08 -070063import com.android.contacts.model.Contact;
64import com.android.contacts.model.ContactLoader;
65import com.android.contacts.model.RawContact;
Chiao Cheng428f0082012-11-13 18:38:56 -080066import com.android.contacts.common.model.account.AccountType;
Maurice Chu851222a2012-06-21 11:43:08 -070067import com.android.contacts.model.dataitem.DataItem;
Chiao Cheng428f0082012-11-13 18:38:56 -080068import com.android.contacts.common.model.dataitem.DataKind;
Maurice Chu851222a2012-06-21 11:43:08 -070069import com.android.contacts.model.dataitem.EmailDataItem;
70import com.android.contacts.model.dataitem.ImDataItem;
Chiao Chengd9eab4d2012-11-14 18:24:25 -080071import com.android.contacts.common.util.Constants;
Yorke Lee2e385832013-09-09 14:42:01 -070072import com.android.contacts.common.util.UriUtils;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070073import com.android.contacts.util.DataStatus;
74import com.android.contacts.util.ImageViewDrawableSetter;
75import com.android.contacts.util.SchedulingUtils;
Chiao Cheng9f2a5e72012-10-30 09:34:32 -070076import com.android.contacts.common.util.StopWatch;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070077import com.google.common.base.Preconditions;
78import com.google.common.collect.Lists;
79
Daniel Lehmannedb576a2011-07-27 16:45:13 -070080import java.util.HashMap;
81import java.util.HashSet;
82import java.util.List;
83import java.util.Set;
84
85// TODO: Save selected tab index during rotation
Daniel Lehmannedb576a2011-07-27 16:45:13 -070086
Daniel Lehmannedb576a2011-07-27 16:45:13 -070087/**
88 * Mostly translucent {@link Activity} that shows QuickContact dialog. It loads
89 * data asynchronously, and then shows a popup with details centered around
90 * {@link Intent#getSourceBounds()}.
91 */
92public class QuickContactActivity extends Activity {
93 private static final String TAG = "QuickContact";
94
95 private static final boolean TRACE_LAUNCH = false;
96 private static final String TRACE_TAG = "quickcontact";
Josh Gargusee287e92012-04-06 20:11:33 -070097 private static final int POST_DRAW_WAIT_DURATION = 60;
Makoto Onukidfe8cc82012-05-17 18:03:44 -070098 private static final boolean ENABLE_STOPWATCH = false;
99
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700100
101 @SuppressWarnings("deprecation")
102 private static final String LEGACY_AUTHORITY = android.provider.Contacts.AUTHORITY;
103
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700104 private Uri mLookupUri;
105 private String[] mExcludeMimes;
106 private List<String> mSortedActionMimeTypes = Lists.newArrayList();
107
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700108 private FloatingChildLayout mFloatingLayout;
109
110 private View mPhotoContainer;
111 private ViewGroup mTrack;
112 private HorizontalScrollView mTrackScroller;
113 private View mSelectedTabRectangle;
Daniel Lehmann1792b082011-08-23 18:48:22 -0700114 private View mLineAfterTrack;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700115
Yorke Lee62196682013-09-06 18:34:17 -0700116 private ImageView mPhotoView;
Yorke Lee9d85a2a2012-11-07 17:03:08 -0800117 private ImageView mOpenDetailsImage;
Christine Chen72b3ab12013-08-13 23:22:34 +0200118 private ImageView mStarImage;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700119 private ViewPager mListPager;
Chiao Cheng35ad0c32013-02-12 12:25:45 -0800120 private ViewPagerAdapter mPagerAdapter;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700121
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700122 private ContactLoader mContactLoader;
123
Josh Gargus9758a922012-03-08 17:12:42 -0800124 private final ImageViewDrawableSetter mPhotoSetter = new ImageViewDrawableSetter();
125
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700126 /**
127 * Keeps the default action per mimetype. Empty if no default actions are set
128 */
129 private HashMap<String, Action> mDefaultsMap = new HashMap<String, Action>();
130
131 /**
132 * Set of {@link Action} that are associated with the aggregate currently
133 * displayed by this dialog, represented as a map from {@link String}
134 * MIME-type to a list of {@link Action}.
135 */
136 private ActionMultiMap mActions = new ActionMultiMap();
137
138 /**
139 * {@link #LEADING_MIMETYPES} and {@link #TRAILING_MIMETYPES} are used to sort MIME-types.
140 *
141 * <p>The MIME-types in {@link #LEADING_MIMETYPES} appear in the front of the dialog,
142 * in the order specified here.</p>
143 *
144 * <p>The ones in {@link #TRAILING_MIMETYPES} appear in the end of the dialog, in the order
145 * specified here.</p>
146 *
147 * <p>The rest go between them, in the order in the array.</p>
148 */
149 private static final List<String> LEADING_MIMETYPES = Lists.newArrayList(
150 Phone.CONTENT_ITEM_TYPE, SipAddress.CONTENT_ITEM_TYPE, Email.CONTENT_ITEM_TYPE);
151
152 /** See {@link #LEADING_MIMETYPES}. */
153 private static final List<String> TRAILING_MIMETYPES = Lists.newArrayList(
154 StructuredPostal.CONTENT_ITEM_TYPE, Website.CONTENT_ITEM_TYPE);
155
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200156 /** Id for the background loader */
157 private static final int LOADER_ID = 0;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700158
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700159 private StopWatch mStopWatch = ENABLE_STOPWATCH
160 ? StopWatch.start("QuickContact") : StopWatch.getNullStopWatch();
161
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700162 @Override
163 protected void onCreate(Bundle icicle) {
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700164 mStopWatch.lap("c"); // create start
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700165 super.onCreate(icicle);
166
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700167 mStopWatch.lap("sc"); // super.onCreate
168
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700169 if (TRACE_LAUNCH) android.os.Debug.startMethodTracing(TRACE_TAG);
170
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700171 // Parse intent
172 final Intent intent = getIntent();
173
174 Uri lookupUri = intent.getData();
175
176 // Check to see whether it comes from the old version.
177 if (lookupUri != null && LEGACY_AUTHORITY.equals(lookupUri.getAuthority())) {
178 final long rawContactId = ContentUris.parseId(lookupUri);
179 lookupUri = RawContacts.getContactLookupUri(getContentResolver(),
180 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
181 }
182
183 mLookupUri = Preconditions.checkNotNull(lookupUri, "missing lookupUri");
184
185 mExcludeMimes = intent.getStringArrayExtra(QuickContact.EXTRA_EXCLUDE_MIMES);
186
187 mStopWatch.lap("i"); // intent parsed
188
189 mContactLoader = (ContactLoader) getLoaderManager().initLoader(
190 LOADER_ID, null, mLoaderCallbacks);
191
192 mStopWatch.lap("ld"); // loader started
193
Daisuke Miyakawa10d7df72011-07-29 16:11:05 -0700194 // Show QuickContact in front of soft input
195 getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
196 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
197
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700198 setContentView(R.layout.quickcontact_activity);
199
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700200 mStopWatch.lap("l"); // layout inflated
201
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700202 mFloatingLayout = (FloatingChildLayout) findViewById(R.id.floating_layout);
203 mTrack = (ViewGroup) findViewById(R.id.track);
204 mTrackScroller = (HorizontalScrollView) findViewById(R.id.track_scroller);
Yorke Lee9d85a2a2012-11-07 17:03:08 -0800205 mOpenDetailsImage = (ImageView) findViewById(R.id.contact_details_image);
Christine Chen72b3ab12013-08-13 23:22:34 +0200206 mStarImage = (ImageView) findViewById(R.id.quickcontact_star_button);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700207 mListPager = (ViewPager) findViewById(R.id.item_list_pager);
208 mSelectedTabRectangle = findViewById(R.id.selected_tab_rectangle);
Daniel Lehmann1792b082011-08-23 18:48:22 -0700209 mLineAfterTrack = findViewById(R.id.line_after_track);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700210
211 mFloatingLayout.setOnOutsideTouchListener(new View.OnTouchListener() {
212 @Override
213 public boolean onTouch(View v, MotionEvent event) {
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700214 handleOutsideTouch();
215 return true;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700216 }
217 });
218
219 final OnClickListener openDetailsClickHandler = new OnClickListener() {
220 @Override
221 public void onClick(View v) {
222 final Intent intent = new Intent(Intent.ACTION_VIEW, mLookupUri);
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700223 mContactLoader.cacheResult();
Adam Powell8ca93ed2012-04-23 13:28:28 -0700224 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700225 startActivity(intent);
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700226 close(false);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700227 }
228 };
Christine Chen72b3ab12013-08-13 23:22:34 +0200229 mOpenDetailsImage.setOnClickListener(openDetailsClickHandler);
230
Chiao Cheng35ad0c32013-02-12 12:25:45 -0800231 mPagerAdapter = new ViewPagerAdapter(getFragmentManager());
232 mListPager.setAdapter(mPagerAdapter);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700233 mListPager.setOnPageChangeListener(new PageChangeListener());
234
Daniel Lehmannd1e50112012-05-06 16:41:15 -0700235 final Rect sourceBounds = intent.getSourceBounds();
Makoto Onuki802fa622012-05-15 15:24:00 -0700236 if (sourceBounds != null) {
237 mFloatingLayout.setChildTargetScreen(sourceBounds);
238 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700239
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700240 // find and prepare correct header view
241 mPhotoContainer = findViewById(R.id.photo_container);
Yorke Lee62196682013-09-06 18:34:17 -0700242
Katherine Kuan81281ee2011-07-28 16:20:59 -0700243 setHeaderNameText(R.id.name, R.string.missing_name);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700244
Yorke Lee62196682013-09-06 18:34:17 -0700245 mPhotoView = (ImageView) mPhotoContainer.findViewById(R.id.photo);
246 mPhotoView.setOnClickListener(openDetailsClickHandler);
247
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700248 mStopWatch.lap("v"); // view initialized
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700249
Daniel Lehmann3a53c732012-05-17 19:18:58 -0700250 SchedulingUtils.doAfterLayout(mFloatingLayout, new Runnable() {
251 @Override
252 public void run() {
253 mFloatingLayout.fadeInBackground();
254 }
255 });
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700256
257 mStopWatch.lap("cf"); // onCreate finished
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700258 }
259
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700260 private void handleOutsideTouch() {
261 if (mFloatingLayout.isContentFullyVisible()) {
262 close(true);
263 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700264 }
265
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700266 private void close(boolean withAnimation) {
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700267 // cancel any pending queries
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200268 getLoaderManager().destroyLoader(LOADER_ID);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700269
270 if (withAnimation) {
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700271 mFloatingLayout.fadeOutBackground();
272 final boolean animated = mFloatingLayout.hideContent(new Runnable() {
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700273 @Override
274 public void run() {
Josh Gargusee287e92012-04-06 20:11:33 -0700275 // Wait until the final animation frame has been drawn, otherwise
276 // there is jank as the framework transitions to the next Activity.
277 SchedulingUtils.doAfterDraw(mFloatingLayout, new Runnable() {
278 @Override
279 public void run() {
280 // Unfortunately, we need to also use postDelayed() to wait a moment
281 // for the frame to be drawn, else the framework's activity-transition
282 // animation will kick in before the final frame is available to it.
283 // This seems unavoidable. The problem isn't merely that there is no
284 // post-draw listener API; if that were so, it would be sufficient to
285 // call post() instead of postDelayed().
286 new Handler().postDelayed(new Runnable() {
287 @Override
288 public void run() {
289 finish();
290 }
291 }, POST_DRAW_WAIT_DURATION);
292 }
293 });
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700294 }
295 });
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700296 if (!animated) {
297 // If we were in the wrong state, simply quit (this can happen for example
298 // if the user pushes BACK before anything has loaded)
299 finish();
300 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700301 } else {
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700302 finish();
303 }
304 }
305
306 @Override
307 public void onBackPressed() {
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700308 close(true);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700309 }
310
Katherine Kuan81281ee2011-07-28 16:20:59 -0700311 /** Assign this string to the view if it is not empty. */
312 private void setHeaderNameText(int id, int resId) {
313 setHeaderNameText(id, getText(resId));
314 }
315
316 /** Assign this string to the view if it is not empty. */
317 private void setHeaderNameText(int id, CharSequence value) {
318 final View view = mPhotoContainer.findViewById(id);
319 if (view instanceof TextView) {
320 if (!TextUtils.isEmpty(value)) {
321 ((TextView)view).setText(value);
322 }
323 }
324 }
325
326 /**
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700327 * Check if the given MIME-type appears in the list of excluded MIME-types
328 * that the most-recent caller requested.
329 */
330 private boolean isMimeExcluded(String mimeType) {
331 if (mExcludeMimes == null) return false;
332 for (String excludedMime : mExcludeMimes) {
333 if (TextUtils.equals(excludedMime, mimeType)) {
334 return true;
335 }
336 }
337 return false;
338 }
339
340 /**
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200341 * Handle the result from the ContactLoader
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700342 */
Maurice Chu851222a2012-06-21 11:43:08 -0700343 private void bindData(Contact data) {
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700344 final ResolveCache cache = ResolveCache.getInstance(this);
345 final Context context = this;
346
Yorke Lee9d85a2a2012-11-07 17:03:08 -0800347 mOpenDetailsImage.setVisibility(isMimeExcluded(Contacts.CONTENT_ITEM_TYPE) ? View.GONE
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700348 : View.VISIBLE);
Christine Chen72b3ab12013-08-13 23:22:34 +0200349 final boolean isStarred = data.getStarred();
350 if (isStarred) {
351 mStarImage.setImageResource(R.drawable.ic_favorite_on_lt);
352 } else {
353 mStarImage.setImageResource(R.drawable.ic_favorite_off_lt);
354 }
355 final Uri lookupUri = data.getLookupUri();
Christine Chen72b3ab12013-08-13 23:22:34 +0200356
Yorke Lee2e385832013-09-09 14:42:01 -0700357 // If this is a json encoded URI, there is no local contact to star
358 if (UriUtils.isEncodedContactUri(lookupUri)) {
359 mStarImage.setVisibility(View.GONE);
360 } else {
361 mStarImage.setVisibility(View.VISIBLE);
362 mStarImage.setOnClickListener(new OnClickListener() {
363 @Override
364 public void onClick(View view) {
365 // Toggle "starred" state
366 // Make sure there is a contact
367 if (lookupUri != null) {
368 // Changes the state of the image already before sending updates to the
369 // database
370 if (isStarred) {
371 mStarImage.setImageResource(R.drawable.ic_favorite_off_lt);
372 } else {
373 mStarImage.setImageResource(R.drawable.ic_favorite_on_lt);
374 }
375
376 // Now perform the real save
377 final Intent intent = ContactSaveService.createSetStarredIntent(context,
378 lookupUri, !isStarred);
379 context.startService(intent);
380 }
Christine Chen72b3ab12013-08-13 23:22:34 +0200381 }
Yorke Lee2e385832013-09-09 14:42:01 -0700382 });
383 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700384
385 mDefaultsMap.clear();
386
Maurice Chu851222a2012-06-21 11:43:08 -0700387 mStopWatch.lap("sph"); // Start photo setting
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700388
Yorke Lee62196682013-09-06 18:34:17 -0700389 mPhotoSetter.setupContactPhoto(data, mPhotoView);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700390
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700391 mStopWatch.lap("ph"); // Photo set
392
Maurice Chu851222a2012-06-21 11:43:08 -0700393 for (RawContact rawContact : data.getRawContacts()) {
394 for (DataItem dataItem : rawContact.getDataItems()) {
395 final String mimeType = dataItem.getMimeType();
Chiao Cheng47b6f702012-09-07 17:28:17 -0700396 final AccountType accountType = rawContact.getAccountType(this);
397 final DataKind dataKind = AccountTypeManager.getInstance(this)
398 .getKindOrFallback(accountType, mimeType);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700399
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200400 // Skip this data item if MIME-type excluded
401 if (isMimeExcluded(mimeType)) continue;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700402
Maurice Chu851222a2012-06-21 11:43:08 -0700403 final long dataId = dataItem.getId();
404 final boolean isPrimary = dataItem.isPrimary();
405 final boolean isSuperPrimary = dataItem.isSuperPrimary();
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700406
Chiao Cheng47b6f702012-09-07 17:28:17 -0700407 if (dataKind != null) {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200408 // Build an action for this data entry, find a mapping to a UI
409 // element, build its summary from the cursor, and collect it
410 // along with all others of this MIME-type.
Chiao Cheng47b6f702012-09-07 17:28:17 -0700411 final Action action = new DataAction(context, dataItem, dataKind);
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700412 final boolean wasAdded = considerAdd(action, cache, isSuperPrimary);
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200413 if (wasAdded) {
414 // Remember the default
415 if (isSuperPrimary || (isPrimary && (mDefaultsMap.get(mimeType) == null))) {
416 mDefaultsMap.put(mimeType, action);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700417 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700418 }
419 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700420
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200421 // Handle Email rows with presence data as Im entry
Daniel Lehmann9daca142011-12-20 11:41:22 +0200422 final DataStatus status = data.getStatuses().get(dataId);
Maurice Chu851222a2012-06-21 11:43:08 -0700423 if (status != null && dataItem instanceof EmailDataItem) {
424 final EmailDataItem email = (EmailDataItem) dataItem;
425 final ImDataItem im = ImDataItem.createFromEmail(email);
Chiao Cheng47b6f702012-09-07 17:28:17 -0700426 if (dataKind != null) {
427 final DataAction action = new DataAction(context, im, dataKind);
Daniel Lehmann9daca142011-12-20 11:41:22 +0200428 action.setPresence(status.getPresence());
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700429 considerAdd(action, cache, isSuperPrimary);
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200430 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700431 }
432 }
433 }
434
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700435 mStopWatch.lap("e"); // Entities inflated
436
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700437 // Collapse Action Lists (remove e.g. duplicate e-mail addresses from different sources)
438 for (List<Action> actionChildren : mActions.values()) {
439 Collapser.collapseList(actionChildren);
440 }
441
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700442 mStopWatch.lap("c"); // List collapsed
443
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200444 setHeaderNameText(R.id.name, data.getDisplayName());
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700445
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700446 // All the mime-types to add.
447 final Set<String> containedTypes = new HashSet<String>(mActions.keySet());
448 mSortedActionMimeTypes.clear();
449 // First, add LEADING_MIMETYPES, which are most common.
450 for (String mimeType : LEADING_MIMETYPES) {
451 if (containedTypes.contains(mimeType)) {
452 mSortedActionMimeTypes.add(mimeType);
453 containedTypes.remove(mimeType);
454 }
455 }
456
457 // Add all the remaining ones that are not TRAILING
458 for (String mimeType : containedTypes.toArray(new String[containedTypes.size()])) {
459 if (!TRAILING_MIMETYPES.contains(mimeType)) {
460 mSortedActionMimeTypes.add(mimeType);
461 containedTypes.remove(mimeType);
462 }
463 }
464
465 // Then, add TRAILING_MIMETYPES, which are least common.
466 for (String mimeType : TRAILING_MIMETYPES) {
467 if (containedTypes.contains(mimeType)) {
468 containedTypes.remove(mimeType);
469 mSortedActionMimeTypes.add(mimeType);
470 }
471 }
Chiao Cheng35ad0c32013-02-12 12:25:45 -0800472 mPagerAdapter.notifyDataSetChanged();
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700473
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700474 mStopWatch.lap("mt"); // Mime types initialized
475
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700476 // Add buttons for each mimetype
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200477 mTrack.removeAllViews();
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700478 for (String mimeType : mSortedActionMimeTypes) {
Jay Shraunerd683b542013-01-11 14:55:16 -0800479 final View actionView = inflateAction(mimeType, cache, mTrack, data.getDisplayName());
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700480 mTrack.addView(actionView);
481 }
482
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700483 mStopWatch.lap("mt"); // Buttons added
484
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700485 final boolean hasData = !mSortedActionMimeTypes.isEmpty();
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700486 mTrackScroller.setVisibility(hasData ? View.VISIBLE : View.GONE);
487 mSelectedTabRectangle.setVisibility(hasData ? View.VISIBLE : View.GONE);
Daniel Lehmann1792b082011-08-23 18:48:22 -0700488 mLineAfterTrack.setVisibility(hasData ? View.VISIBLE : View.GONE);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700489 mListPager.setVisibility(hasData ? View.VISIBLE : View.GONE);
490 }
491
492 /**
493 * Consider adding the given {@link Action}, which will only happen if
494 * {@link PackageManager} finds an application to handle
495 * {@link Action#getIntent()}.
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700496 * @param action the action to handle
497 * @param resolveCache cache of applications that can handle actions
498 * @param front indicates whether to add the action to the front of the list
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700499 * @return true if action has been added
500 */
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700501 private boolean considerAdd(Action action, ResolveCache resolveCache, boolean front) {
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700502 if (resolveCache.hasResolve(action)) {
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700503 mActions.put(action.getMimeType(), action, front);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700504 return true;
505 }
506 return false;
507 }
508
509 /**
510 * Inflate the in-track view for the action of the given MIME-type, collapsing duplicate values.
511 * Will use the icon provided by the {@link DataKind}.
512 */
Jay Shraunerd683b542013-01-11 14:55:16 -0800513 private View inflateAction(String mimeType, ResolveCache resolveCache,
514 ViewGroup root, String name) {
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700515 final CheckableImageView typeView = (CheckableImageView) getLayoutInflater().inflate(
516 R.layout.quickcontact_track_button, root, false);
517
518 List<Action> children = mActions.get(mimeType);
519 typeView.setTag(mimeType);
520 final Action firstInfo = children.get(0);
521
522 // Set icon and listen for clicks
Jay Shraunerd683b542013-01-11 14:55:16 -0800523 final CharSequence descrip = resolveCache.getDescription(firstInfo, name);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700524 final Drawable icon = resolveCache.getIcon(firstInfo);
525 typeView.setChecked(false);
526 typeView.setContentDescription(descrip);
527 typeView.setImageDrawable(icon);
528 typeView.setOnClickListener(mTypeViewClickListener);
529
530 return typeView;
531 }
532
533 private CheckableImageView getActionViewAt(int position) {
534 return (CheckableImageView) mTrack.getChildAt(position);
535 }
536
537 @Override
538 public void onAttachFragment(Fragment fragment) {
539 final QuickContactListFragment listFragment = (QuickContactListFragment) fragment;
540 listFragment.setListener(mListFragmentListener);
541 }
542
Maurice Chu851222a2012-06-21 11:43:08 -0700543 private LoaderCallbacks<Contact> mLoaderCallbacks =
544 new LoaderCallbacks<Contact>() {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200545 @Override
Maurice Chu851222a2012-06-21 11:43:08 -0700546 public void onLoaderReset(Loader<Contact> loader) {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200547 }
548
549 @Override
Maurice Chu851222a2012-06-21 11:43:08 -0700550 public void onLoadFinished(Loader<Contact> loader, Contact data) {
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700551 mStopWatch.lap("lf"); // onLoadFinished
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200552 if (isFinishing()) {
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700553 close(false);
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200554 return;
555 }
556 if (data.isError()) {
557 // This shouldn't ever happen, so throw an exception. The {@link ContactLoader}
558 // should log the actual exception.
559 throw new IllegalStateException("Failed to load contact", data.getException());
560 }
561 if (data.isNotFound()) {
562 Log.i(TAG, "No contact found: " + ((ContactLoader)loader).getLookupUri());
563 Toast.makeText(QuickContactActivity.this, R.string.invalidContactMessage,
564 Toast.LENGTH_LONG).show();
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700565 close(false);
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200566 return;
567 }
568
569 bindData(data);
570
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700571 mStopWatch.lap("bd"); // bindData finished
572
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700573 if (TRACE_LAUNCH) android.os.Debug.stopMethodTracing();
Makoto Onukifc0a89f2012-05-14 17:37:34 -0700574 if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
575 Log.d(Constants.PERFORMANCE_TAG, "QuickContact shown");
576 }
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200577
578 // Data bound and ready, pull curtain to show. Put this on the Handler to ensure
579 // that the layout passes are completed
Josh Gargus6f5557e2012-03-21 10:45:08 -0700580 SchedulingUtils.doAfterLayout(mFloatingLayout, new Runnable() {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200581 @Override
582 public void run() {
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700583 mFloatingLayout.showContent(new Runnable() {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200584 @Override
585 public void run() {
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700586 mContactLoader.upgradeToFullContact();
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200587 }
588 });
589 }
590 });
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700591 mStopWatch.stopAndLog(TAG, 0);
592 mStopWatch = StopWatch.getNullStopWatch(); // We're done with it.
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200593 }
594
595 @Override
Maurice Chu851222a2012-06-21 11:43:08 -0700596 public Loader<Contact> onCreateLoader(int id, Bundle args) {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200597 if (mLookupUri == null) {
598 Log.wtf(TAG, "Lookup uri wasn't initialized. Loader was started too early");
599 }
Yorke Leeb2b435a2012-11-12 16:47:06 -0800600 return new ContactLoader(getApplicationContext(), mLookupUri,
Paul Sliwowski45f23d42013-08-27 15:29:40 -0700601 false /*loadGroupMetaData*/, false /*loadInvitableAccountTypes*/,
602 false /*postViewNotification*/, true /*computeFormattedPhoneNumber*/);
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200603 }
604 };
605
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700606 /** A type (e.g. Call/Addresses was clicked) */
607 private final OnClickListener mTypeViewClickListener = new OnClickListener() {
608 @Override
609 public void onClick(View view) {
610 final CheckableImageView actionView = (CheckableImageView)view;
611 final String mimeType = (String) actionView.getTag();
612 int index = mSortedActionMimeTypes.indexOf(mimeType);
613 mListPager.setCurrentItem(index, true);
614 }
615 };
616
617 private class ViewPagerAdapter extends FragmentPagerAdapter {
618 public ViewPagerAdapter(FragmentManager fragmentManager) {
619 super(fragmentManager);
620 }
621
622 @Override
623 public Fragment getItem(int position) {
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700624 final String mimeType = mSortedActionMimeTypes.get(position);
Chiao Cheng35ad0c32013-02-12 12:25:45 -0800625 QuickContactListFragment fragment = new QuickContactListFragment(mimeType);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700626 final List<Action> actions = mActions.get(mimeType);
627 fragment.setActions(actions);
628 return fragment;
629 }
630
631 @Override
632 public int getCount() {
633 return mSortedActionMimeTypes.size();
634 }
Chiao Cheng35ad0c32013-02-12 12:25:45 -0800635
636 @Override
637 public int getItemPosition(Object object) {
638 final QuickContactListFragment fragment = (QuickContactListFragment) object;
639 final String mimeType = fragment.getMimeType();
640 for (int i = 0; i < mSortedActionMimeTypes.size(); i++) {
641 if (mimeType.equals(mSortedActionMimeTypes.get(i))) {
642 return i;
643 }
644 }
645 return PagerAdapter.POSITION_NONE;
646 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700647 }
648
649 private class PageChangeListener extends SimpleOnPageChangeListener {
Yorke Lee421c9382012-12-12 18:36:35 -0800650 private int mScrollingState = ViewPager.SCROLL_STATE_IDLE;
651
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700652 @Override
653 public void onPageSelected(int position) {
654 final CheckableImageView actionView = getActionViewAt(position);
655 mTrackScroller.requestChildRectangleOnScreen(actionView,
656 new Rect(0, 0, actionView.getWidth(), actionView.getHeight()), false);
Yorke Lee421c9382012-12-12 18:36:35 -0800657 // Don't render rectangle if we are currently scrolling to prevent it from flickering
658 if (mScrollingState == ViewPager.SCROLL_STATE_IDLE) {
659 renderSelectedRectangle(position, 0);
660 }
661 }
662
663 @Override
664 public void onPageScrollStateChanged(int state) {
665 super.onPageScrollStateChanged(state);
666 mScrollingState = state;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700667 }
668
669 @Override
670 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
Chiao Cheng4c290902012-11-12 15:53:48 -0800671 renderSelectedRectangle(position, positionOffset);
672 }
673
674 private void renderSelectedRectangle(int position, float positionOffset) {
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700675 final RelativeLayout.LayoutParams layoutParams =
676 (RelativeLayout.LayoutParams) mSelectedTabRectangle.getLayoutParams();
Chiao Cheng4c290902012-11-12 15:53:48 -0800677 final int width = layoutParams.width;
Yorke Lee9e4b7972013-04-08 11:29:48 -0700678 layoutParams.setMarginStart((int) ((position + positionOffset) * width));
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700679 mSelectedTabRectangle.setLayoutParams(layoutParams);
680 }
681 }
682
683 private final QuickContactListFragment.Listener mListFragmentListener =
684 new QuickContactListFragment.Listener() {
685 @Override
686 public void onOutsideClick() {
687 // If there is no background, we want to dismiss, because to the user it seems
688 // like he had touched outside. If the ViewPager is solid however, those taps
689 // must be ignored
690 final boolean isTransparent = mListPager.getBackground() == null;
691 if (isTransparent) handleOutsideTouch();
692 }
693
694 @Override
695 public void onItemClicked(final Action action, final boolean alternate) {
696 final Runnable startAppRunnable = new Runnable() {
697 @Override
698 public void run() {
699 try {
700 startActivity(alternate ? action.getAlternateIntent() : action.getIntent());
701 } catch (ActivityNotFoundException e) {
702 Toast.makeText(QuickContactActivity.this, R.string.quickcontact_missing_app,
703 Toast.LENGTH_SHORT).show();
704 }
705
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700706 close(false);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700707 }
708 };
709 // Defer the action to make the window properly repaint
710 new Handler().post(startAppRunnable);
711 }
712 };
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700713}