QuickContactActivity.java revision 1792b08c500ed528e7d21401affd936b3a7b3dc6
1/* 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 19import com.android.contacts.Collapser; 20import com.android.contacts.ContactPresenceIconUtil; 21import com.android.contacts.R; 22import com.android.contacts.model.AccountTypeManager; 23import com.android.contacts.model.DataKind; 24import com.android.contacts.util.ContactBadgeUtil; 25import com.android.contacts.util.DataStatus; 26import com.android.contacts.util.NotifyingAsyncQueryHandler; 27import com.android.contacts.util.NotifyingAsyncQueryHandler.AsyncQueryListener; 28import com.google.common.base.Preconditions; 29import com.google.common.collect.Lists; 30 31import android.app.Activity; 32import android.app.Fragment; 33import android.app.FragmentManager; 34import android.content.ActivityNotFoundException; 35import android.content.ContentUris; 36import android.content.Context; 37import android.content.Intent; 38import android.content.pm.PackageManager; 39import android.content.res.AssetFileDescriptor; 40import android.database.Cursor; 41import android.graphics.Bitmap; 42import android.graphics.BitmapFactory; 43import android.graphics.Rect; 44import android.graphics.drawable.Drawable; 45import android.net.Uri; 46import android.os.AsyncTask; 47import android.os.Bundle; 48import android.os.Handler; 49import android.provider.ContactsContract.CommonDataKinds.Email; 50import android.provider.ContactsContract.CommonDataKinds.Im; 51import android.provider.ContactsContract.CommonDataKinds.Phone; 52import android.provider.ContactsContract.CommonDataKinds.Photo; 53import android.provider.ContactsContract.CommonDataKinds.SipAddress; 54import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; 55import android.provider.ContactsContract.CommonDataKinds.Website; 56import android.provider.ContactsContract.Contacts; 57import android.provider.ContactsContract.Data; 58import android.provider.ContactsContract.DisplayPhoto; 59import android.provider.ContactsContract.QuickContact; 60import android.provider.ContactsContract.RawContacts; 61import android.support.v13.app.FragmentPagerAdapter; 62import android.support.v4.view.ViewPager; 63import android.support.v4.view.ViewPager.SimpleOnPageChangeListener; 64import android.text.TextUtils; 65import android.util.Log; 66import android.view.MotionEvent; 67import android.view.View; 68import android.view.View.OnClickListener; 69import android.view.ViewGroup; 70import android.view.WindowManager; 71import android.widget.HorizontalScrollView; 72import android.widget.ImageButton; 73import android.widget.ImageView; 74import android.widget.RelativeLayout; 75import android.widget.TextView; 76import android.widget.Toast; 77 78import java.io.IOException; 79import java.util.HashMap; 80import java.util.HashSet; 81import java.util.List; 82import java.util.Set; 83 84// TODO: Save selected tab index during rotation 85 86/** 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"; 96 97 @SuppressWarnings("deprecation") 98 private static final String LEGACY_AUTHORITY = android.provider.Contacts.AUTHORITY; 99 100 private NotifyingAsyncQueryHandler mHandler; 101 102 private Uri mLookupUri; 103 private String[] mExcludeMimes; 104 private List<String> mSortedActionMimeTypes = Lists.newArrayList(); 105 106 private boolean mHasFinishedAnimatingIn = false; 107 private boolean mHasStartedAnimatingOut = false; 108 109 private FloatingChildLayout mFloatingLayout; 110 111 private View mPhotoContainer; 112 private ViewGroup mTrack; 113 private HorizontalScrollView mTrackScroller; 114 private View mSelectedTabRectangle; 115 private View mLineAfterTrack; 116 117 private ImageButton mOpenDetailsButton; 118 private ImageButton mOpenDetailsPushLayerButton; 119 private ViewPager mListPager; 120 121 /** 122 * Keeps the default action per mimetype. Empty if no default actions are set 123 */ 124 private HashMap<String, Action> mDefaultsMap = new HashMap<String, Action>(); 125 126 /** 127 * Set of {@link Action} that are associated with the aggregate currently 128 * displayed by this dialog, represented as a map from {@link String} 129 * MIME-type to a list of {@link Action}. 130 */ 131 private ActionMultiMap mActions = new ActionMultiMap(); 132 133 /** 134 * {@link #LEADING_MIMETYPES} and {@link #TRAILING_MIMETYPES} are used to sort MIME-types. 135 * 136 * <p>The MIME-types in {@link #LEADING_MIMETYPES} appear in the front of the dialog, 137 * in the order specified here.</p> 138 * 139 * <p>The ones in {@link #TRAILING_MIMETYPES} appear in the end of the dialog, in the order 140 * specified here.</p> 141 * 142 * <p>The rest go between them, in the order in the array.</p> 143 */ 144 private static final List<String> LEADING_MIMETYPES = Lists.newArrayList( 145 Phone.CONTENT_ITEM_TYPE, SipAddress.CONTENT_ITEM_TYPE, Email.CONTENT_ITEM_TYPE); 146 147 /** See {@link #LEADING_MIMETYPES}. */ 148 private static final List<String> TRAILING_MIMETYPES = Lists.newArrayList( 149 StructuredPostal.CONTENT_ITEM_TYPE, Website.CONTENT_ITEM_TYPE); 150 151 /** Id for the background handler that loads the data */ 152 private static final int HANDLER_ID_DATA = 1; 153 154 @Override 155 protected void onCreate(Bundle icicle) { 156 super.onCreate(icicle); 157 158 // Show QuickContact in front of soft input 159 getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, 160 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 161 162 setContentView(R.layout.quickcontact_activity); 163 164 mFloatingLayout = (FloatingChildLayout) findViewById(R.id.floating_layout); 165 mTrack = (ViewGroup) findViewById(R.id.track); 166 mTrackScroller = (HorizontalScrollView) findViewById(R.id.track_scroller); 167 mOpenDetailsButton = (ImageButton) findViewById(R.id.open_details_button); 168 mOpenDetailsPushLayerButton = (ImageButton) findViewById(R.id.open_details_push_layer); 169 mListPager = (ViewPager) findViewById(R.id.item_list_pager); 170 mSelectedTabRectangle = findViewById(R.id.selected_tab_rectangle); 171 mLineAfterTrack = findViewById(R.id.line_after_track); 172 173 mFloatingLayout.setOnOutsideTouchListener(new View.OnTouchListener() { 174 @Override 175 public boolean onTouch(View v, MotionEvent event) { 176 return handleOutsideTouch(); 177 } 178 }); 179 180 final OnClickListener openDetailsClickHandler = new OnClickListener() { 181 @Override 182 public void onClick(View v) { 183 final Intent intent = new Intent(Intent.ACTION_VIEW, mLookupUri); 184 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); 185 startActivity(intent); 186 hide(false); 187 } 188 }; 189 mOpenDetailsButton.setOnClickListener(openDetailsClickHandler); 190 mOpenDetailsPushLayerButton.setOnClickListener(openDetailsClickHandler); 191 mListPager.setAdapter(new ViewPagerAdapter(getFragmentManager())); 192 mListPager.setOnPageChangeListener(new PageChangeListener()); 193 194 mHandler = new NotifyingAsyncQueryHandler(this, mQueryListener); 195 196 show(); 197 } 198 199 private void show() { 200 201 if (TRACE_LAUNCH) { 202 android.os.Debug.startMethodTracing(TRACE_TAG); 203 } 204 205 final Intent intent = getIntent(); 206 207 Uri lookupUri = intent.getData(); 208 209 // Check to see whether it comes from the old version. 210 if (LEGACY_AUTHORITY.equals(lookupUri.getAuthority())) { 211 final long rawContactId = ContentUris.parseId(lookupUri); 212 lookupUri = RawContacts.getContactLookupUri(getContentResolver(), 213 ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId)); 214 } 215 216 mLookupUri = Preconditions.checkNotNull(lookupUri, "missing lookupUri"); 217 218 // Read requested parameters for displaying 219 final Rect targetScreen = intent.getSourceBounds(); 220 Preconditions.checkNotNull(targetScreen, "missing targetScreen"); 221 mFloatingLayout.setChildTargetScreen(targetScreen); 222 223 mExcludeMimes = intent.getStringArrayExtra(QuickContact.EXTRA_EXCLUDE_MIMES); 224 225 // find and prepare correct header view 226 mPhotoContainer = findViewById(R.id.photo_container); 227 setHeaderNameText(R.id.name, R.string.missing_name); 228 setHeaderText(R.id.status, null); 229 setHeaderText(R.id.timestamp, null); 230 setHeaderImage(R.id.presence, null); 231 232 // Start background query for data, but only select photo rows when they 233 // directly match the super-primary PHOTO_ID. 234 final Uri dataUri = Uri.withAppendedPath(lookupUri, Contacts.Data.CONTENT_DIRECTORY); 235 mHandler.cancelOperation(HANDLER_ID_DATA); 236 237 // Select all data items of the contact (except for photos, where we only select the display 238 // photo) 239 mHandler.startQuery(HANDLER_ID_DATA, lookupUri, dataUri, DataQuery.PROJECTION, Data.MIMETYPE 240 + "!=? OR (" + Data.MIMETYPE + "=? AND " + Data._ID + "=" + Contacts.PHOTO_ID 241 + ")", new String[] { Photo.CONTENT_ITEM_TYPE, Photo.CONTENT_ITEM_TYPE }, null); 242 } 243 244 private boolean handleOutsideTouch() { 245 if (!mHasFinishedAnimatingIn) return false; 246 if (mHasStartedAnimatingOut) return false; 247 248 mHasStartedAnimatingOut = true; 249 hide(true); 250 return true; 251 } 252 253 private void hide(boolean withAnimation) { 254 // cancel any pending queries 255 mHandler.cancelOperation(HANDLER_ID_DATA); 256 257 if (withAnimation) { 258 mFloatingLayout.hideChild(new Runnable() { 259 @Override 260 public void run() { 261 finish(); 262 } 263 }); 264 } else { 265 mFloatingLayout.hideChild(null); 266 finish(); 267 } 268 } 269 270 @Override 271 public void onBackPressed() { 272 hide(true); 273 } 274 275 private final AsyncQueryListener mQueryListener = new AsyncQueryListener() { 276 @Override 277 public synchronized void onQueryComplete(int token, Object cookie, Cursor cursor) { 278 try { 279 if (isFinishing()) { 280 hide(false); 281 return; 282 } else if (cursor == null || cursor.getCount() == 0) { 283 Toast.makeText(QuickContactActivity.this, R.string.invalidContactMessage, 284 Toast.LENGTH_LONG).show(); 285 hide(false); 286 return; 287 } 288 289 bindData(cursor); 290 291 if (TRACE_LAUNCH) { 292 android.os.Debug.stopMethodTracing(); 293 } 294 295 // Data bound and ready, pull curtain to show. Put this on the Handler to ensure 296 // that the layout passes are completed 297 mHandler.post(new Runnable() { 298 @Override 299 public void run() { 300 mFloatingLayout.showChild(new Runnable() { 301 @Override 302 public void run() { 303 mHasFinishedAnimatingIn = true; 304 } 305 }); 306 } 307 }); 308 } finally { 309 if (cursor != null) { 310 cursor.close(); 311 } 312 } 313 } 314 }; 315 316 /** Assign this string to the view if it is not empty. */ 317 private void setHeaderNameText(int id, int resId) { 318 setHeaderNameText(id, getText(resId)); 319 } 320 321 /** Assign this string to the view if it is not empty. */ 322 private void setHeaderNameText(int id, CharSequence value) { 323 final View view = mPhotoContainer.findViewById(id); 324 if (view instanceof TextView) { 325 if (!TextUtils.isEmpty(value)) { 326 ((TextView)view).setText(value); 327 } 328 } 329 } 330 331 /** 332 * Assign this string to the view (if found in {@link #mPhotoContainer}), or hiding this view 333 * if there is no string. 334 */ 335 private void setHeaderText(int id, int resId) { 336 setHeaderText(id, getText(resId)); 337 } 338 339 /** 340 * Assign this string to the view (if found in {@link #mPhotoContainer}), or hiding this view 341 * if there is no string. 342 */ 343 private void setHeaderText(int id, CharSequence value) { 344 final View view = mPhotoContainer.findViewById(id); 345 if (view instanceof TextView) { 346 ((TextView)view).setText(value); 347 view.setVisibility(TextUtils.isEmpty(value) ? View.GONE : View.VISIBLE); 348 } 349 } 350 351 /** Assign this image to the view, if found in {@link #mPhotoContainer}. */ 352 private void setHeaderImage(int id, Drawable drawable) { 353 final View view = mPhotoContainer.findViewById(id); 354 if (view instanceof ImageView) { 355 ((ImageView)view).setImageDrawable(drawable); 356 view.setVisibility(drawable == null ? View.GONE : View.VISIBLE); 357 } 358 } 359 360 /** 361 * Check if the given MIME-type appears in the list of excluded MIME-types 362 * that the most-recent caller requested. 363 */ 364 private boolean isMimeExcluded(String mimeType) { 365 if (mExcludeMimes == null) return false; 366 for (String excludedMime : mExcludeMimes) { 367 if (TextUtils.equals(excludedMime, mimeType)) { 368 return true; 369 } 370 } 371 return false; 372 } 373 374 /** 375 * Handle the result from the {@link #TOKEN_DATA} query. 376 */ 377 private void bindData(Cursor cursor) { 378 final ResolveCache cache = ResolveCache.getInstance(this); 379 final Context context = this; 380 381 mOpenDetailsButton.setVisibility(isMimeExcluded(Contacts.CONTENT_ITEM_TYPE) ? View.GONE 382 : View.VISIBLE); 383 384 mDefaultsMap.clear(); 385 386 final DataStatus status = new DataStatus(); 387 final AccountTypeManager accountTypes = AccountTypeManager.getInstance( 388 context.getApplicationContext()); 389 final ImageView photoView = (ImageView) mPhotoContainer.findViewById(R.id.photo); 390 391 Bitmap photoBitmap = null; 392 while (cursor.moveToNext()) { 393 // Handle any social status updates from this row 394 status.possibleUpdate(cursor); 395 396 final String mimeType = cursor.getString(DataQuery.MIMETYPE); 397 398 // Skip this data item if MIME-type excluded 399 if (isMimeExcluded(mimeType)) continue; 400 401 final long dataId = cursor.getLong(DataQuery._ID); 402 final String accountType = cursor.getString(DataQuery.ACCOUNT_TYPE); 403 final String dataSet = cursor.getString(DataQuery.DATA_SET); 404 final boolean isPrimary = cursor.getInt(DataQuery.IS_PRIMARY) != 0; 405 final boolean isSuperPrimary = cursor.getInt(DataQuery.IS_SUPER_PRIMARY) != 0; 406 407 // Handle photos included as data row 408 if (Photo.CONTENT_ITEM_TYPE.equals(mimeType)) { 409 final int displayPhotoColumnIndex = cursor.getColumnIndex(Photo.PHOTO_FILE_ID); 410 final boolean hasDisplayPhoto = !cursor.isNull(displayPhotoColumnIndex); 411 if (hasDisplayPhoto) { 412 final long displayPhotoId = cursor.getLong(displayPhotoColumnIndex); 413 final Uri displayPhotoUri = ContentUris.withAppendedId( 414 DisplayPhoto.CONTENT_URI, displayPhotoId); 415 // Fetch and JPEG uncompress on the background thread 416 new AsyncTask<Void, Void, Bitmap>() { 417 @Override 418 protected Bitmap doInBackground(Void... params) { 419 try { 420 AssetFileDescriptor fd = getContentResolver() 421 .openAssetFileDescriptor(displayPhotoUri, "r"); 422 return BitmapFactory.decodeStream(fd.createInputStream()); 423 } catch (IOException e) { 424 Log.e(TAG, "Error getting display photo. Ignoring, as we already " + 425 "have the thumbnail", e); 426 return null; 427 } 428 } 429 430 @Override 431 protected void onPostExecute(Bitmap result) { 432 if (result == null) return; 433 photoView.setImageBitmap(result); 434 } 435 }.execute(); 436 } 437 final int photoColumnIndex = cursor.getColumnIndex(Photo.PHOTO); 438 final byte[] photoBlob = cursor.getBlob(photoColumnIndex); 439 if (photoBlob != null) { 440 photoBitmap = BitmapFactory.decodeByteArray(photoBlob, 0, photoBlob.length); 441 } 442 continue; 443 } 444 445 final DataKind kind = accountTypes.getKindOrFallback(accountType, dataSet, mimeType); 446 447 if (kind != null) { 448 // Build an action for this data entry, find a mapping to a UI 449 // element, build its summary from the cursor, and collect it 450 // along with all others of this MIME-type. 451 final Action action = new DataAction(context, mimeType, kind, dataId, cursor); 452 final boolean wasAdded = considerAdd(action, cache); 453 if (wasAdded) { 454 // Remember the default 455 if (isSuperPrimary || (isPrimary && (mDefaultsMap.get(mimeType) == null))) { 456 mDefaultsMap.put(mimeType, action); 457 } 458 } 459 } 460 461 // Handle Email rows with presence data as Im entry 462 final boolean hasPresence = !cursor.isNull(DataQuery.PRESENCE); 463 if (hasPresence && Email.CONTENT_ITEM_TYPE.equals(mimeType)) { 464 final DataKind imKind = accountTypes.getKindOrFallback(accountType, dataSet, 465 Im.CONTENT_ITEM_TYPE); 466 if (imKind != null) { 467 final DataAction action = new DataAction(context, Im.CONTENT_ITEM_TYPE, imKind, 468 dataId, cursor); 469 considerAdd(action, cache); 470 } 471 } 472 } 473 474 // Collapse Action Lists (remove e.g. duplicate e-mail addresses from different sources) 475 for (List<Action> actionChildren : mActions.values()) { 476 Collapser.collapseList(actionChildren); 477 } 478 479 if (cursor.moveToLast()) { 480 // Read contact information from last data row 481 final String name = cursor.getString(DataQuery.DISPLAY_NAME); 482 final int presence = cursor.getInt(DataQuery.CONTACT_PRESENCE); 483 final int chatCapability = cursor.getInt(DataQuery.CONTACT_CHAT_CAPABILITY); 484 final Drawable statusIcon = ContactPresenceIconUtil.getChatCapabilityIcon( 485 context, presence, chatCapability); 486 487 setHeaderNameText(R.id.name, name); 488 // TODO: Bring this back once we have a design 489// setHeaderImage(R.id.presence, statusIcon); 490 } 491 492 if (photoView != null) { 493 // Place photo when discovered in data, otherwise show generic avatar 494 photoView.setImageBitmap(photoBitmap != null ? photoBitmap 495 : ContactBadgeUtil.loadPlaceholderPhoto(context)); 496 } 497 498 // TODO: Bring this back once we have a design 499// if (status.isValid()) { 500// // Update status when valid was found 501// setHeaderText(R.id.status, status.getStatus()); 502// setHeaderText(R.id.timestamp, status.getTimestampLabel(context)); 503// } 504 505 // All the mime-types to add. 506 final Set<String> containedTypes = new HashSet<String>(mActions.keySet()); 507 mSortedActionMimeTypes.clear(); 508 // First, add LEADING_MIMETYPES, which are most common. 509 for (String mimeType : LEADING_MIMETYPES) { 510 if (containedTypes.contains(mimeType)) { 511 mSortedActionMimeTypes.add(mimeType); 512 containedTypes.remove(mimeType); 513 } 514 } 515 516 // Add all the remaining ones that are not TRAILING 517 for (String mimeType : containedTypes.toArray(new String[containedTypes.size()])) { 518 if (!TRAILING_MIMETYPES.contains(mimeType)) { 519 mSortedActionMimeTypes.add(mimeType); 520 containedTypes.remove(mimeType); 521 } 522 } 523 524 // Then, add TRAILING_MIMETYPES, which are least common. 525 for (String mimeType : TRAILING_MIMETYPES) { 526 if (containedTypes.contains(mimeType)) { 527 containedTypes.remove(mimeType); 528 mSortedActionMimeTypes.add(mimeType); 529 } 530 } 531 532 // Add buttons for each mimetype 533 for (String mimeType : mSortedActionMimeTypes) { 534 final View actionView = inflateAction(mimeType, cache, mTrack); 535 mTrack.addView(actionView); 536 } 537 538 final boolean hasData = !mSortedActionMimeTypes.isEmpty(); 539 mTrackScroller.setVisibility(hasData ? View.VISIBLE : View.GONE); 540 mSelectedTabRectangle.setVisibility(hasData ? View.VISIBLE : View.GONE); 541 mLineAfterTrack.setVisibility(hasData ? View.VISIBLE : View.GONE); 542 mListPager.setVisibility(hasData ? View.VISIBLE : View.GONE); 543 } 544 545 /** 546 * Consider adding the given {@link Action}, which will only happen if 547 * {@link PackageManager} finds an application to handle 548 * {@link Action#getIntent()}. 549 * @return true if action has been added 550 */ 551 private boolean considerAdd(Action action, ResolveCache resolveCache) { 552 if (resolveCache.hasResolve(action)) { 553 mActions.put(action.getMimeType(), action); 554 return true; 555 } 556 return false; 557 } 558 559 /** 560 * Inflate the in-track view for the action of the given MIME-type, collapsing duplicate values. 561 * Will use the icon provided by the {@link DataKind}. 562 */ 563 private View inflateAction(String mimeType, ResolveCache resolveCache, ViewGroup root) { 564 final CheckableImageView typeView = (CheckableImageView) getLayoutInflater().inflate( 565 R.layout.quickcontact_track_button, root, false); 566 567 List<Action> children = mActions.get(mimeType); 568 typeView.setTag(mimeType); 569 final Action firstInfo = children.get(0); 570 571 // Set icon and listen for clicks 572 final CharSequence descrip = resolveCache.getDescription(firstInfo); 573 final Drawable icon = resolveCache.getIcon(firstInfo); 574 typeView.setChecked(false); 575 typeView.setContentDescription(descrip); 576 typeView.setImageDrawable(icon); 577 typeView.setOnClickListener(mTypeViewClickListener); 578 579 return typeView; 580 } 581 582 private CheckableImageView getActionViewAt(int position) { 583 return (CheckableImageView) mTrack.getChildAt(position); 584 } 585 586 @Override 587 public void onAttachFragment(Fragment fragment) { 588 final QuickContactListFragment listFragment = (QuickContactListFragment) fragment; 589 listFragment.setListener(mListFragmentListener); 590 } 591 592 /** A type (e.g. Call/Addresses was clicked) */ 593 private final OnClickListener mTypeViewClickListener = new OnClickListener() { 594 @Override 595 public void onClick(View view) { 596 final CheckableImageView actionView = (CheckableImageView)view; 597 final String mimeType = (String) actionView.getTag(); 598 int index = mSortedActionMimeTypes.indexOf(mimeType); 599 mListPager.setCurrentItem(index, true); 600 } 601 }; 602 603 private class ViewPagerAdapter extends FragmentPagerAdapter { 604 public ViewPagerAdapter(FragmentManager fragmentManager) { 605 super(fragmentManager); 606 } 607 608 @Override 609 public Fragment getItem(int position) { 610 QuickContactListFragment fragment = new QuickContactListFragment(); 611 final String mimeType = mSortedActionMimeTypes.get(position); 612 final List<Action> actions = mActions.get(mimeType); 613 fragment.setActions(actions); 614 return fragment; 615 } 616 617 @Override 618 public int getCount() { 619 return mSortedActionMimeTypes.size(); 620 } 621 } 622 623 private class PageChangeListener extends SimpleOnPageChangeListener { 624 @Override 625 public void onPageSelected(int position) { 626 final CheckableImageView actionView = getActionViewAt(position); 627 mTrackScroller.requestChildRectangleOnScreen(actionView, 628 new Rect(0, 0, actionView.getWidth(), actionView.getHeight()), false); 629 } 630 631 @Override 632 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 633 final RelativeLayout.LayoutParams layoutParams = 634 (RelativeLayout.LayoutParams) mSelectedTabRectangle.getLayoutParams(); 635 final int width = mSelectedTabRectangle.getWidth(); 636 layoutParams.leftMargin = (int) ((position + positionOffset) * width); 637 mSelectedTabRectangle.setLayoutParams(layoutParams); 638 } 639 } 640 641 private final QuickContactListFragment.Listener mListFragmentListener = 642 new QuickContactListFragment.Listener() { 643 @Override 644 public void onOutsideClick() { 645 // If there is no background, we want to dismiss, because to the user it seems 646 // like he had touched outside. If the ViewPager is solid however, those taps 647 // must be ignored 648 final boolean isTransparent = mListPager.getBackground() == null; 649 if (isTransparent) handleOutsideTouch(); 650 } 651 652 @Override 653 public void onItemClicked(final Action action, final boolean alternate) { 654 final Runnable startAppRunnable = new Runnable() { 655 @Override 656 public void run() { 657 try { 658 startActivity(alternate ? action.getAlternateIntent() : action.getIntent()); 659 } catch (ActivityNotFoundException e) { 660 Toast.makeText(QuickContactActivity.this, R.string.quickcontact_missing_app, 661 Toast.LENGTH_SHORT).show(); 662 } 663 664 hide(false); 665 } 666 }; 667 // Defer the action to make the window properly repaint 668 new Handler().post(startAppRunnable); 669 } 670 }; 671 672 private interface DataQuery { 673 final String[] PROJECTION = new String[] { 674 Data._ID, 675 676 RawContacts.ACCOUNT_TYPE, 677 RawContacts.DATA_SET, 678 Contacts.STARRED, 679 Contacts.DISPLAY_NAME, 680 Contacts.CONTACT_PRESENCE, 681 Contacts.CONTACT_CHAT_CAPABILITY, 682 683 Data.STATUS, 684 Data.STATUS_RES_PACKAGE, 685 Data.STATUS_ICON, 686 Data.STATUS_LABEL, 687 Data.STATUS_TIMESTAMP, 688 Data.PRESENCE, 689 Data.CHAT_CAPABILITY, 690 691 Data.RES_PACKAGE, 692 Data.MIMETYPE, 693 Data.IS_PRIMARY, 694 Data.IS_SUPER_PRIMARY, 695 Data.RAW_CONTACT_ID, 696 697 Data.DATA1, Data.DATA2, Data.DATA3, Data.DATA4, Data.DATA5, 698 Data.DATA6, Data.DATA7, Data.DATA8, Data.DATA9, Data.DATA10, Data.DATA11, 699 Data.DATA12, Data.DATA13, Data.DATA14, Data.DATA15, 700 }; 701 702 final int _ID = 0; 703 704 final int ACCOUNT_TYPE = 1; 705 final int DATA_SET = 2; 706 final int STARRED = 3; 707 final int DISPLAY_NAME = 4; 708 final int CONTACT_PRESENCE = 5; 709 final int CONTACT_CHAT_CAPABILITY = 6; 710 711 final int STATUS = 7; 712 final int STATUS_RES_PACKAGE = 8; 713 final int STATUS_ICON = 9; 714 final int STATUS_LABEL = 10; 715 final int STATUS_TIMESTAMP = 11; 716 final int PRESENCE = 12; 717 final int CHAT_CAPABILITY = 13; 718 719 final int RES_PACKAGE = 14; 720 final int MIMETYPE = 15; 721 final int IS_PRIMARY = 16; 722 final int IS_SUPER_PRIMARY = 17; 723 } 724} 725