blob: 2ca122dcc21496c33bad3c81e4f5d3861b4fa7a5 [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;
54import android.widget.ImageButton;
55import android.widget.ImageView;
56import android.widget.RelativeLayout;
57import android.widget.TextView;
58import android.widget.Toast;
59
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;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070072import com.android.contacts.util.DataStatus;
73import com.android.contacts.util.ImageViewDrawableSetter;
74import com.android.contacts.util.SchedulingUtils;
Chiao Cheng9f2a5e72012-10-30 09:34:32 -070075import com.android.contacts.common.util.StopWatch;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070076import com.google.common.base.Preconditions;
77import com.google.common.collect.Lists;
78
Daniel Lehmannedb576a2011-07-27 16:45:13 -070079import java.util.HashMap;
80import java.util.HashSet;
81import java.util.List;
82import java.util.Set;
83
84// TODO: Save selected tab index during rotation
Daniel Lehmannedb576a2011-07-27 16:45:13 -070085
Daniel Lehmannedb576a2011-07-27 16:45:13 -070086/**
87 * Mostly translucent {@link Activity} that shows QuickContact dialog. It loads
88 * data asynchronously, and then shows a popup with details centered around
89 * {@link Intent#getSourceBounds()}.
90 */
91public class QuickContactActivity extends Activity {
92 private static final String TAG = "QuickContact";
93
94 private static final boolean TRACE_LAUNCH = false;
95 private static final String TRACE_TAG = "quickcontact";
Josh Gargusee287e92012-04-06 20:11:33 -070096 private static final int POST_DRAW_WAIT_DURATION = 60;
Makoto Onukidfe8cc82012-05-17 18:03:44 -070097 private static final boolean ENABLE_STOPWATCH = false;
98
Daniel Lehmannedb576a2011-07-27 16:45:13 -070099
100 @SuppressWarnings("deprecation")
101 private static final String LEGACY_AUTHORITY = android.provider.Contacts.AUTHORITY;
102
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700103 private Uri mLookupUri;
104 private String[] mExcludeMimes;
105 private List<String> mSortedActionMimeTypes = Lists.newArrayList();
106
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700107 private FloatingChildLayout mFloatingLayout;
108
109 private View mPhotoContainer;
110 private ViewGroup mTrack;
111 private HorizontalScrollView mTrackScroller;
112 private View mSelectedTabRectangle;
Daniel Lehmann1792b082011-08-23 18:48:22 -0700113 private View mLineAfterTrack;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700114
Yorke Lee9d85a2a2012-11-07 17:03:08 -0800115 private ImageView mOpenDetailsImage;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700116 private ImageButton mOpenDetailsPushLayerButton;
117 private ViewPager mListPager;
Chiao Cheng35ad0c32013-02-12 12:25:45 -0800118 private ViewPagerAdapter mPagerAdapter;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700119
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700120 private ContactLoader mContactLoader;
121
Josh Gargus9758a922012-03-08 17:12:42 -0800122 private final ImageViewDrawableSetter mPhotoSetter = new ImageViewDrawableSetter();
123
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700124 /**
125 * Keeps the default action per mimetype. Empty if no default actions are set
126 */
127 private HashMap<String, Action> mDefaultsMap = new HashMap<String, Action>();
128
129 /**
130 * Set of {@link Action} that are associated with the aggregate currently
131 * displayed by this dialog, represented as a map from {@link String}
132 * MIME-type to a list of {@link Action}.
133 */
134 private ActionMultiMap mActions = new ActionMultiMap();
135
136 /**
137 * {@link #LEADING_MIMETYPES} and {@link #TRAILING_MIMETYPES} are used to sort MIME-types.
138 *
139 * <p>The MIME-types in {@link #LEADING_MIMETYPES} appear in the front of the dialog,
140 * in the order specified here.</p>
141 *
142 * <p>The ones in {@link #TRAILING_MIMETYPES} appear in the end of the dialog, in the order
143 * specified here.</p>
144 *
145 * <p>The rest go between them, in the order in the array.</p>
146 */
147 private static final List<String> LEADING_MIMETYPES = Lists.newArrayList(
148 Phone.CONTENT_ITEM_TYPE, SipAddress.CONTENT_ITEM_TYPE, Email.CONTENT_ITEM_TYPE);
149
150 /** See {@link #LEADING_MIMETYPES}. */
151 private static final List<String> TRAILING_MIMETYPES = Lists.newArrayList(
152 StructuredPostal.CONTENT_ITEM_TYPE, Website.CONTENT_ITEM_TYPE);
153
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200154 /** Id for the background loader */
155 private static final int LOADER_ID = 0;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700156
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700157 private StopWatch mStopWatch = ENABLE_STOPWATCH
158 ? StopWatch.start("QuickContact") : StopWatch.getNullStopWatch();
159
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700160 @Override
161 protected void onCreate(Bundle icicle) {
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700162 mStopWatch.lap("c"); // create start
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700163 super.onCreate(icicle);
164
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700165 mStopWatch.lap("sc"); // super.onCreate
166
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700167 if (TRACE_LAUNCH) android.os.Debug.startMethodTracing(TRACE_TAG);
168
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700169 // Parse intent
170 final Intent intent = getIntent();
171
172 Uri lookupUri = intent.getData();
173
174 // Check to see whether it comes from the old version.
175 if (lookupUri != null && LEGACY_AUTHORITY.equals(lookupUri.getAuthority())) {
176 final long rawContactId = ContentUris.parseId(lookupUri);
177 lookupUri = RawContacts.getContactLookupUri(getContentResolver(),
178 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId));
179 }
180
181 mLookupUri = Preconditions.checkNotNull(lookupUri, "missing lookupUri");
182
183 mExcludeMimes = intent.getStringArrayExtra(QuickContact.EXTRA_EXCLUDE_MIMES);
184
185 mStopWatch.lap("i"); // intent parsed
186
187 mContactLoader = (ContactLoader) getLoaderManager().initLoader(
188 LOADER_ID, null, mLoaderCallbacks);
189
190 mStopWatch.lap("ld"); // loader started
191
Daisuke Miyakawa10d7df72011-07-29 16:11:05 -0700192 // Show QuickContact in front of soft input
193 getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
194 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
195
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700196 setContentView(R.layout.quickcontact_activity);
197
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700198 mStopWatch.lap("l"); // layout inflated
199
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700200 mFloatingLayout = (FloatingChildLayout) findViewById(R.id.floating_layout);
201 mTrack = (ViewGroup) findViewById(R.id.track);
202 mTrackScroller = (HorizontalScrollView) findViewById(R.id.track_scroller);
Yorke Lee9d85a2a2012-11-07 17:03:08 -0800203 mOpenDetailsImage = (ImageView) findViewById(R.id.contact_details_image);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700204 mOpenDetailsPushLayerButton = (ImageButton) findViewById(R.id.open_details_push_layer);
205 mListPager = (ViewPager) findViewById(R.id.item_list_pager);
206 mSelectedTabRectangle = findViewById(R.id.selected_tab_rectangle);
Daniel Lehmann1792b082011-08-23 18:48:22 -0700207 mLineAfterTrack = findViewById(R.id.line_after_track);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700208
209 mFloatingLayout.setOnOutsideTouchListener(new View.OnTouchListener() {
210 @Override
211 public boolean onTouch(View v, MotionEvent event) {
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700212 handleOutsideTouch();
213 return true;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700214 }
215 });
216
217 final OnClickListener openDetailsClickHandler = new OnClickListener() {
218 @Override
219 public void onClick(View v) {
220 final Intent intent = new Intent(Intent.ACTION_VIEW, mLookupUri);
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700221 mContactLoader.cacheResult();
Adam Powell8ca93ed2012-04-23 13:28:28 -0700222 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700223 startActivity(intent);
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700224 close(false);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700225 }
226 };
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700227 mOpenDetailsPushLayerButton.setOnClickListener(openDetailsClickHandler);
Chiao Cheng35ad0c32013-02-12 12:25:45 -0800228 mPagerAdapter = new ViewPagerAdapter(getFragmentManager());
229 mListPager.setAdapter(mPagerAdapter);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700230 mListPager.setOnPageChangeListener(new PageChangeListener());
231
Daniel Lehmannd1e50112012-05-06 16:41:15 -0700232 final Rect sourceBounds = intent.getSourceBounds();
Makoto Onuki802fa622012-05-15 15:24:00 -0700233 if (sourceBounds != null) {
234 mFloatingLayout.setChildTargetScreen(sourceBounds);
235 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700236
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700237 // find and prepare correct header view
238 mPhotoContainer = findViewById(R.id.photo_container);
Katherine Kuan81281ee2011-07-28 16:20:59 -0700239 setHeaderNameText(R.id.name, R.string.missing_name);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700240
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700241 mStopWatch.lap("v"); // view initialized
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700242
Daniel Lehmann3a53c732012-05-17 19:18:58 -0700243 SchedulingUtils.doAfterLayout(mFloatingLayout, new Runnable() {
244 @Override
245 public void run() {
246 mFloatingLayout.fadeInBackground();
247 }
248 });
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700249
250 mStopWatch.lap("cf"); // onCreate finished
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700251 }
252
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700253 private void handleOutsideTouch() {
254 if (mFloatingLayout.isContentFullyVisible()) {
255 close(true);
256 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700257 }
258
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700259 private void close(boolean withAnimation) {
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700260 // cancel any pending queries
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200261 getLoaderManager().destroyLoader(LOADER_ID);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700262
263 if (withAnimation) {
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700264 mFloatingLayout.fadeOutBackground();
265 final boolean animated = mFloatingLayout.hideContent(new Runnable() {
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700266 @Override
267 public void run() {
Josh Gargusee287e92012-04-06 20:11:33 -0700268 // Wait until the final animation frame has been drawn, otherwise
269 // there is jank as the framework transitions to the next Activity.
270 SchedulingUtils.doAfterDraw(mFloatingLayout, new Runnable() {
271 @Override
272 public void run() {
273 // Unfortunately, we need to also use postDelayed() to wait a moment
274 // for the frame to be drawn, else the framework's activity-transition
275 // animation will kick in before the final frame is available to it.
276 // This seems unavoidable. The problem isn't merely that there is no
277 // post-draw listener API; if that were so, it would be sufficient to
278 // call post() instead of postDelayed().
279 new Handler().postDelayed(new Runnable() {
280 @Override
281 public void run() {
282 finish();
283 }
284 }, POST_DRAW_WAIT_DURATION);
285 }
286 });
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700287 }
288 });
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700289 if (!animated) {
290 // If we were in the wrong state, simply quit (this can happen for example
291 // if the user pushes BACK before anything has loaded)
292 finish();
293 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700294 } else {
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700295 finish();
296 }
297 }
298
299 @Override
300 public void onBackPressed() {
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700301 close(true);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700302 }
303
Katherine Kuan81281ee2011-07-28 16:20:59 -0700304 /** Assign this string to the view if it is not empty. */
305 private void setHeaderNameText(int id, int resId) {
306 setHeaderNameText(id, getText(resId));
307 }
308
309 /** Assign this string to the view if it is not empty. */
310 private void setHeaderNameText(int id, CharSequence value) {
311 final View view = mPhotoContainer.findViewById(id);
312 if (view instanceof TextView) {
313 if (!TextUtils.isEmpty(value)) {
314 ((TextView)view).setText(value);
315 }
316 }
317 }
318
319 /**
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700320 * Check if the given MIME-type appears in the list of excluded MIME-types
321 * that the most-recent caller requested.
322 */
323 private boolean isMimeExcluded(String mimeType) {
324 if (mExcludeMimes == null) return false;
325 for (String excludedMime : mExcludeMimes) {
326 if (TextUtils.equals(excludedMime, mimeType)) {
327 return true;
328 }
329 }
330 return false;
331 }
332
333 /**
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200334 * Handle the result from the ContactLoader
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700335 */
Maurice Chu851222a2012-06-21 11:43:08 -0700336 private void bindData(Contact data) {
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700337 final ResolveCache cache = ResolveCache.getInstance(this);
338 final Context context = this;
339
Yorke Lee9d85a2a2012-11-07 17:03:08 -0800340 mOpenDetailsImage.setVisibility(isMimeExcluded(Contacts.CONTENT_ITEM_TYPE) ? View.GONE
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700341 : View.VISIBLE);
342
343 mDefaultsMap.clear();
344
Maurice Chu851222a2012-06-21 11:43:08 -0700345 mStopWatch.lap("sph"); // Start photo setting
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700346
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700347 final ImageView photoView = (ImageView) mPhotoContainer.findViewById(R.id.photo);
Josh Gargus9758a922012-03-08 17:12:42 -0800348 mPhotoSetter.setupContactPhoto(data, photoView);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700349
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700350 mStopWatch.lap("ph"); // Photo set
351
Maurice Chu851222a2012-06-21 11:43:08 -0700352 for (RawContact rawContact : data.getRawContacts()) {
353 for (DataItem dataItem : rawContact.getDataItems()) {
354 final String mimeType = dataItem.getMimeType();
Chiao Cheng47b6f702012-09-07 17:28:17 -0700355 final AccountType accountType = rawContact.getAccountType(this);
356 final DataKind dataKind = AccountTypeManager.getInstance(this)
357 .getKindOrFallback(accountType, mimeType);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700358
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200359 // Skip this data item if MIME-type excluded
360 if (isMimeExcluded(mimeType)) continue;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700361
Maurice Chu851222a2012-06-21 11:43:08 -0700362 final long dataId = dataItem.getId();
363 final boolean isPrimary = dataItem.isPrimary();
364 final boolean isSuperPrimary = dataItem.isSuperPrimary();
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700365
Chiao Cheng47b6f702012-09-07 17:28:17 -0700366 if (dataKind != null) {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200367 // Build an action for this data entry, find a mapping to a UI
368 // element, build its summary from the cursor, and collect it
369 // along with all others of this MIME-type.
Chiao Cheng47b6f702012-09-07 17:28:17 -0700370 final Action action = new DataAction(context, dataItem, dataKind);
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700371 final boolean wasAdded = considerAdd(action, cache, isSuperPrimary);
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200372 if (wasAdded) {
373 // Remember the default
374 if (isSuperPrimary || (isPrimary && (mDefaultsMap.get(mimeType) == null))) {
375 mDefaultsMap.put(mimeType, action);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700376 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700377 }
378 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700379
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200380 // Handle Email rows with presence data as Im entry
Daniel Lehmann9daca142011-12-20 11:41:22 +0200381 final DataStatus status = data.getStatuses().get(dataId);
Maurice Chu851222a2012-06-21 11:43:08 -0700382 if (status != null && dataItem instanceof EmailDataItem) {
383 final EmailDataItem email = (EmailDataItem) dataItem;
384 final ImDataItem im = ImDataItem.createFromEmail(email);
Chiao Cheng47b6f702012-09-07 17:28:17 -0700385 if (dataKind != null) {
386 final DataAction action = new DataAction(context, im, dataKind);
Daniel Lehmann9daca142011-12-20 11:41:22 +0200387 action.setPresence(status.getPresence());
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700388 considerAdd(action, cache, isSuperPrimary);
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200389 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700390 }
391 }
392 }
393
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700394 mStopWatch.lap("e"); // Entities inflated
395
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700396 // Collapse Action Lists (remove e.g. duplicate e-mail addresses from different sources)
397 for (List<Action> actionChildren : mActions.values()) {
398 Collapser.collapseList(actionChildren);
399 }
400
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700401 mStopWatch.lap("c"); // List collapsed
402
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200403 setHeaderNameText(R.id.name, data.getDisplayName());
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700404
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700405 // All the mime-types to add.
406 final Set<String> containedTypes = new HashSet<String>(mActions.keySet());
407 mSortedActionMimeTypes.clear();
408 // First, add LEADING_MIMETYPES, which are most common.
409 for (String mimeType : LEADING_MIMETYPES) {
410 if (containedTypes.contains(mimeType)) {
411 mSortedActionMimeTypes.add(mimeType);
412 containedTypes.remove(mimeType);
413 }
414 }
415
416 // Add all the remaining ones that are not TRAILING
417 for (String mimeType : containedTypes.toArray(new String[containedTypes.size()])) {
418 if (!TRAILING_MIMETYPES.contains(mimeType)) {
419 mSortedActionMimeTypes.add(mimeType);
420 containedTypes.remove(mimeType);
421 }
422 }
423
424 // Then, add TRAILING_MIMETYPES, which are least common.
425 for (String mimeType : TRAILING_MIMETYPES) {
426 if (containedTypes.contains(mimeType)) {
427 containedTypes.remove(mimeType);
428 mSortedActionMimeTypes.add(mimeType);
429 }
430 }
Chiao Cheng35ad0c32013-02-12 12:25:45 -0800431 mPagerAdapter.notifyDataSetChanged();
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700432
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700433 mStopWatch.lap("mt"); // Mime types initialized
434
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700435 // Add buttons for each mimetype
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200436 mTrack.removeAllViews();
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700437 for (String mimeType : mSortedActionMimeTypes) {
Jay Shraunerd683b542013-01-11 14:55:16 -0800438 final View actionView = inflateAction(mimeType, cache, mTrack, data.getDisplayName());
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700439 mTrack.addView(actionView);
440 }
441
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700442 mStopWatch.lap("mt"); // Buttons added
443
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700444 final boolean hasData = !mSortedActionMimeTypes.isEmpty();
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700445 mTrackScroller.setVisibility(hasData ? View.VISIBLE : View.GONE);
446 mSelectedTabRectangle.setVisibility(hasData ? View.VISIBLE : View.GONE);
Daniel Lehmann1792b082011-08-23 18:48:22 -0700447 mLineAfterTrack.setVisibility(hasData ? View.VISIBLE : View.GONE);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700448 mListPager.setVisibility(hasData ? View.VISIBLE : View.GONE);
449 }
450
451 /**
452 * Consider adding the given {@link Action}, which will only happen if
453 * {@link PackageManager} finds an application to handle
454 * {@link Action#getIntent()}.
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700455 * @param action the action to handle
456 * @param resolveCache cache of applications that can handle actions
457 * @param front indicates whether to add the action to the front of the list
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700458 * @return true if action has been added
459 */
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700460 private boolean considerAdd(Action action, ResolveCache resolveCache, boolean front) {
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700461 if (resolveCache.hasResolve(action)) {
Maurice Chu8a0dd0f2012-04-12 16:16:32 -0700462 mActions.put(action.getMimeType(), action, front);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700463 return true;
464 }
465 return false;
466 }
467
468 /**
469 * Inflate the in-track view for the action of the given MIME-type, collapsing duplicate values.
470 * Will use the icon provided by the {@link DataKind}.
471 */
Jay Shraunerd683b542013-01-11 14:55:16 -0800472 private View inflateAction(String mimeType, ResolveCache resolveCache,
473 ViewGroup root, String name) {
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700474 final CheckableImageView typeView = (CheckableImageView) getLayoutInflater().inflate(
475 R.layout.quickcontact_track_button, root, false);
476
477 List<Action> children = mActions.get(mimeType);
478 typeView.setTag(mimeType);
479 final Action firstInfo = children.get(0);
480
481 // Set icon and listen for clicks
Jay Shraunerd683b542013-01-11 14:55:16 -0800482 final CharSequence descrip = resolveCache.getDescription(firstInfo, name);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700483 final Drawable icon = resolveCache.getIcon(firstInfo);
484 typeView.setChecked(false);
485 typeView.setContentDescription(descrip);
486 typeView.setImageDrawable(icon);
487 typeView.setOnClickListener(mTypeViewClickListener);
488
489 return typeView;
490 }
491
492 private CheckableImageView getActionViewAt(int position) {
493 return (CheckableImageView) mTrack.getChildAt(position);
494 }
495
496 @Override
497 public void onAttachFragment(Fragment fragment) {
498 final QuickContactListFragment listFragment = (QuickContactListFragment) fragment;
499 listFragment.setListener(mListFragmentListener);
500 }
501
Maurice Chu851222a2012-06-21 11:43:08 -0700502 private LoaderCallbacks<Contact> mLoaderCallbacks =
503 new LoaderCallbacks<Contact>() {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200504 @Override
Maurice Chu851222a2012-06-21 11:43:08 -0700505 public void onLoaderReset(Loader<Contact> loader) {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200506 }
507
508 @Override
Maurice Chu851222a2012-06-21 11:43:08 -0700509 public void onLoadFinished(Loader<Contact> loader, Contact data) {
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700510 mStopWatch.lap("lf"); // onLoadFinished
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200511 if (isFinishing()) {
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700512 close(false);
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200513 return;
514 }
515 if (data.isError()) {
516 // This shouldn't ever happen, so throw an exception. The {@link ContactLoader}
517 // should log the actual exception.
518 throw new IllegalStateException("Failed to load contact", data.getException());
519 }
520 if (data.isNotFound()) {
521 Log.i(TAG, "No contact found: " + ((ContactLoader)loader).getLookupUri());
522 Toast.makeText(QuickContactActivity.this, R.string.invalidContactMessage,
523 Toast.LENGTH_LONG).show();
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700524 close(false);
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200525 return;
526 }
527
528 bindData(data);
529
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700530 mStopWatch.lap("bd"); // bindData finished
531
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700532 if (TRACE_LAUNCH) android.os.Debug.stopMethodTracing();
Makoto Onukifc0a89f2012-05-14 17:37:34 -0700533 if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
534 Log.d(Constants.PERFORMANCE_TAG, "QuickContact shown");
535 }
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200536
537 // Data bound and ready, pull curtain to show. Put this on the Handler to ensure
538 // that the layout passes are completed
Josh Gargus6f5557e2012-03-21 10:45:08 -0700539 SchedulingUtils.doAfterLayout(mFloatingLayout, new Runnable() {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200540 @Override
541 public void run() {
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700542 mFloatingLayout.showContent(new Runnable() {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200543 @Override
544 public void run() {
Daniel Lehmann9815d7f2012-04-16 18:28:03 -0700545 mContactLoader.upgradeToFullContact();
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200546 }
547 });
548 }
549 });
Makoto Onukidfe8cc82012-05-17 18:03:44 -0700550 mStopWatch.stopAndLog(TAG, 0);
551 mStopWatch = StopWatch.getNullStopWatch(); // We're done with it.
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200552 }
553
554 @Override
Maurice Chu851222a2012-06-21 11:43:08 -0700555 public Loader<Contact> onCreateLoader(int id, Bundle args) {
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200556 if (mLookupUri == null) {
557 Log.wtf(TAG, "Lookup uri wasn't initialized. Loader was started too early");
558 }
Yorke Leeb2b435a2012-11-12 16:47:06 -0800559 return new ContactLoader(getApplicationContext(), mLookupUri,
560 false /*loadGroupMetaData*/, false /*loadStreamItems*/,
561 false /*loadInvitableAccountTypes*/, false /*postViewNotification*/,
562 true /*computeFormattedPhoneNumber*/);
Daniel Lehmanncb8d73f2011-12-16 17:39:50 +0200563 }
564 };
565
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700566 /** A type (e.g. Call/Addresses was clicked) */
567 private final OnClickListener mTypeViewClickListener = new OnClickListener() {
568 @Override
569 public void onClick(View view) {
570 final CheckableImageView actionView = (CheckableImageView)view;
571 final String mimeType = (String) actionView.getTag();
572 int index = mSortedActionMimeTypes.indexOf(mimeType);
573 mListPager.setCurrentItem(index, true);
574 }
575 };
576
577 private class ViewPagerAdapter extends FragmentPagerAdapter {
578 public ViewPagerAdapter(FragmentManager fragmentManager) {
579 super(fragmentManager);
580 }
581
582 @Override
583 public Fragment getItem(int position) {
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700584 final String mimeType = mSortedActionMimeTypes.get(position);
Chiao Cheng35ad0c32013-02-12 12:25:45 -0800585 QuickContactListFragment fragment = new QuickContactListFragment(mimeType);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700586 final List<Action> actions = mActions.get(mimeType);
587 fragment.setActions(actions);
588 return fragment;
589 }
590
591 @Override
592 public int getCount() {
593 return mSortedActionMimeTypes.size();
594 }
Chiao Cheng35ad0c32013-02-12 12:25:45 -0800595
596 @Override
597 public int getItemPosition(Object object) {
598 final QuickContactListFragment fragment = (QuickContactListFragment) object;
599 final String mimeType = fragment.getMimeType();
600 for (int i = 0; i < mSortedActionMimeTypes.size(); i++) {
601 if (mimeType.equals(mSortedActionMimeTypes.get(i))) {
602 return i;
603 }
604 }
605 return PagerAdapter.POSITION_NONE;
606 }
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700607 }
608
609 private class PageChangeListener extends SimpleOnPageChangeListener {
Yorke Lee421c9382012-12-12 18:36:35 -0800610 private int mScrollingState = ViewPager.SCROLL_STATE_IDLE;
611
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700612 @Override
613 public void onPageSelected(int position) {
614 final CheckableImageView actionView = getActionViewAt(position);
615 mTrackScroller.requestChildRectangleOnScreen(actionView,
616 new Rect(0, 0, actionView.getWidth(), actionView.getHeight()), false);
Yorke Lee421c9382012-12-12 18:36:35 -0800617 // Don't render rectangle if we are currently scrolling to prevent it from flickering
618 if (mScrollingState == ViewPager.SCROLL_STATE_IDLE) {
619 renderSelectedRectangle(position, 0);
620 }
621 }
622
623 @Override
624 public void onPageScrollStateChanged(int state) {
625 super.onPageScrollStateChanged(state);
626 mScrollingState = state;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700627 }
628
629 @Override
630 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
Chiao Cheng4c290902012-11-12 15:53:48 -0800631 renderSelectedRectangle(position, positionOffset);
632 }
633
634 private void renderSelectedRectangle(int position, float positionOffset) {
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700635 final RelativeLayout.LayoutParams layoutParams =
636 (RelativeLayout.LayoutParams) mSelectedTabRectangle.getLayoutParams();
Chiao Cheng4c290902012-11-12 15:53:48 -0800637 final int width = layoutParams.width;
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700638 layoutParams.leftMargin = (int) ((position + positionOffset) * width);
639 mSelectedTabRectangle.setLayoutParams(layoutParams);
640 }
641 }
642
643 private final QuickContactListFragment.Listener mListFragmentListener =
644 new QuickContactListFragment.Listener() {
645 @Override
646 public void onOutsideClick() {
647 // If there is no background, we want to dismiss, because to the user it seems
648 // like he had touched outside. If the ViewPager is solid however, those taps
649 // must be ignored
650 final boolean isTransparent = mListPager.getBackground() == null;
651 if (isTransparent) handleOutsideTouch();
652 }
653
654 @Override
655 public void onItemClicked(final Action action, final boolean alternate) {
656 final Runnable startAppRunnable = new Runnable() {
657 @Override
658 public void run() {
659 try {
660 startActivity(alternate ? action.getAlternateIntent() : action.getIntent());
661 } catch (ActivityNotFoundException e) {
662 Toast.makeText(QuickContactActivity.this, R.string.quickcontact_missing_app,
663 Toast.LENGTH_SHORT).show();
664 }
665
Daniel Lehmann2426cb02012-05-10 18:41:21 -0700666 close(false);
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700667 }
668 };
669 // Defer the action to make the window properly repaint
670 new Handler().post(startAppRunnable);
671 }
672 };
Daniel Lehmannedb576a2011-07-27 16:45:13 -0700673}