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