Home | History | Annotate | Download | only in quickcontact
      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