Home | History | Annotate | Download | only in list
      1 /*
      2  * Copyright (C) 2010 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 package com.android.contacts.common.list;
     17 
     18 import android.content.Intent;
     19 import android.content.Loader;
     20 import android.database.Cursor;
     21 import android.net.Uri;
     22 import android.os.Bundle;
     23 import android.text.TextUtils;
     24 import android.util.Log;
     25 import android.view.LayoutInflater;
     26 import android.view.MenuItem;
     27 import android.view.View;
     28 import android.view.View.OnClickListener;
     29 import android.view.ViewGroup;
     30 
     31 import com.android.contacts.common.R;
     32 import com.android.contacts.common.list.ShortcutIntentBuilder.OnShortcutIntentCreatedListener;
     33 import com.android.contacts.common.util.AccountFilterUtil;
     34 import com.android.contacts.commonbind.analytics.AnalyticsUtil;
     35 
     36 import org.json.JSONException;
     37 import org.json.JSONObject;
     38 
     39 /**
     40  * Fragment containing a phone number list for picking.
     41  */
     42 public class PhoneNumberPickerFragment extends ContactEntryListFragment<ContactEntryListAdapter>
     43         implements OnShortcutIntentCreatedListener, PhoneNumberListAdapter.Listener {
     44     private static final String TAG = PhoneNumberPickerFragment.class.getSimpleName();
     45 
     46     private static final int REQUEST_CODE_ACCOUNT_FILTER = 1;
     47 
     48     private static final String KEY_SHORTCUT_ACTION = "shortcutAction";
     49 
     50     private OnPhoneNumberPickerActionListener mListener;
     51     private String mShortcutAction;
     52 
     53     private ContactListFilter mFilter;
     54 
     55     private View mAccountFilterHeader;
     56     /**
     57      * Lives as ListView's header and is shown when {@link #mAccountFilterHeader} is set
     58      * to View.GONE.
     59      */
     60     private View mPaddingView;
     61 
     62     private static final String KEY_FILTER = "filter";
     63 
     64     /** true if the loader has started at least once. */
     65     private boolean mLoaderStarted;
     66 
     67     private boolean mUseCallableUri;
     68 
     69     private ContactListItemView.PhotoPosition mPhotoPosition =
     70             ContactListItemView.getDefaultPhotoPosition(false /* normal/non opposite */);
     71 
     72     /**
     73      * Handles a click on the video call icon for a row in the list.
     74      *
     75      * @param position The position in the list where the click ocurred.
     76      */
     77     @Override
     78     public void onVideoCallIconClicked(int position) {
     79         callNumber(position, true /* isVideoCall */);
     80     }
     81 
     82     private class FilterHeaderClickListener implements OnClickListener {
     83         @Override
     84         public void onClick(View view) {
     85             AccountFilterUtil.startAccountFilterActivityForResult(
     86                     PhoneNumberPickerFragment.this,
     87                     REQUEST_CODE_ACCOUNT_FILTER,
     88                     mFilter);
     89         }
     90     }
     91     private OnClickListener mFilterHeaderClickListener = new FilterHeaderClickListener();
     92 
     93     public PhoneNumberPickerFragment() {
     94         setQuickContactEnabled(false);
     95         setPhotoLoaderEnabled(true);
     96         setSectionHeaderDisplayEnabled(true);
     97         setDirectorySearchMode(DirectoryListLoader.SEARCH_MODE_NONE);
     98 
     99         // Show nothing instead of letting caller Activity show something.
    100         setHasOptionsMenu(true);
    101     }
    102 
    103     public void setDirectorySearchEnabled(boolean flag) {
    104         setDirectorySearchMode(flag ? DirectoryListLoader.SEARCH_MODE_DEFAULT
    105                 : DirectoryListLoader.SEARCH_MODE_NONE);
    106     }
    107 
    108     public void setOnPhoneNumberPickerActionListener(OnPhoneNumberPickerActionListener listener) {
    109         this.mListener = listener;
    110     }
    111 
    112     public OnPhoneNumberPickerActionListener getOnPhoneNumberPickerListener() {
    113         return mListener;
    114     }
    115 
    116     @Override
    117     protected void onCreateView(LayoutInflater inflater, ViewGroup container) {
    118         super.onCreateView(inflater, container);
    119 
    120         View paddingView = inflater.inflate(R.layout.contact_detail_list_padding, null, false);
    121         mPaddingView = paddingView.findViewById(R.id.contact_detail_list_padding);
    122         getListView().addHeaderView(paddingView);
    123 
    124         mAccountFilterHeader = getView().findViewById(R.id.account_filter_header_container);
    125         mAccountFilterHeader.setOnClickListener(mFilterHeaderClickListener);
    126         updateFilterHeaderView();
    127 
    128         setVisibleScrollbarEnabled(getVisibleScrollbarEnabled());
    129     }
    130 
    131     protected boolean getVisibleScrollbarEnabled() {
    132         return true;
    133     }
    134 
    135     @Override
    136     protected void setSearchMode(boolean flag) {
    137         super.setSearchMode(flag);
    138         updateFilterHeaderView();
    139     }
    140 
    141     private void updateFilterHeaderView() {
    142         final ContactListFilter filter = getFilter();
    143         if (mAccountFilterHeader == null || filter == null) {
    144             return;
    145         }
    146         final boolean shouldShowHeader =
    147                 !isSearchMode() &&
    148                 AccountFilterUtil.updateAccountFilterTitleForPhone(
    149                         mAccountFilterHeader, filter, false);
    150         if (shouldShowHeader) {
    151             mPaddingView.setVisibility(View.GONE);
    152             mAccountFilterHeader.setVisibility(View.VISIBLE);
    153         } else {
    154             mPaddingView.setVisibility(View.VISIBLE);
    155             mAccountFilterHeader.setVisibility(View.GONE);
    156         }
    157     }
    158 
    159     @Override
    160     public void restoreSavedState(Bundle savedState) {
    161         super.restoreSavedState(savedState);
    162 
    163         if (savedState == null) {
    164             return;
    165         }
    166 
    167         mFilter = savedState.getParcelable(KEY_FILTER);
    168         mShortcutAction = savedState.getString(KEY_SHORTCUT_ACTION);
    169     }
    170 
    171     @Override
    172     public void onSaveInstanceState(Bundle outState) {
    173         super.onSaveInstanceState(outState);
    174         outState.putParcelable(KEY_FILTER, mFilter);
    175         outState.putString(KEY_SHORTCUT_ACTION, mShortcutAction);
    176     }
    177 
    178     @Override
    179     public boolean onOptionsItemSelected(MenuItem item) {
    180         final int itemId = item.getItemId();
    181         if (itemId == android.R.id.home) {  // See ActionBar#setDisplayHomeAsUpEnabled()
    182             if (mListener != null) {
    183                 mListener.onHomeInActionBarSelected();
    184             }
    185             return true;
    186         }
    187         return super.onOptionsItemSelected(item);
    188     }
    189 
    190     /**
    191      * @param shortcutAction either {@link Intent#ACTION_CALL} or
    192      *            {@link Intent#ACTION_SENDTO} or null.
    193      */
    194     public void setShortcutAction(String shortcutAction) {
    195         this.mShortcutAction = shortcutAction;
    196     }
    197 
    198     @Override
    199     protected void onItemClick(int position, long id) {
    200         callNumber(position, false /* isVideoCall */);
    201     }
    202 
    203     /**
    204      * Initiates a call to the number at the specified position.
    205      *
    206      * @param position The position.
    207      * @param isVideoCall {@code true} if the call should be initiated as a video call,
    208      *      {@code false} otherwise.
    209      */
    210     private void callNumber(int position, boolean isVideoCall) {
    211         final Uri phoneUri = getPhoneUri(position);
    212 
    213         if (phoneUri != null) {
    214             pickPhoneNumber(phoneUri, isVideoCall);
    215         } else {
    216             final String number = getPhoneNumber(position);
    217             if (!TextUtils.isEmpty(number)) {
    218                 cacheContactInfo(position);
    219                 mListener.onPickPhoneNumber(number, isVideoCall,
    220                         getCallInitiationType(true /* isRemoteDirectory */));
    221             } else {
    222                 Log.w(TAG, "Item at " + position + " was clicked before"
    223                         + " adapter is ready. Ignoring");
    224             }
    225         }
    226 
    227         // Get the lookup key and track any analytics
    228         final String lookupKey = getLookupKey(position);
    229         if (!TextUtils.isEmpty(lookupKey)) {
    230             maybeTrackAnalytics(lookupKey);
    231         }
    232     }
    233 
    234     protected void cacheContactInfo(int position) {
    235         // Not implemented. Hook for child classes
    236     }
    237 
    238     protected String getPhoneNumber(int position) {
    239         final PhoneNumberListAdapter adapter = (PhoneNumberListAdapter) getAdapter();
    240         return adapter.getPhoneNumber(position);
    241     }
    242 
    243     protected Uri getPhoneUri(int position) {
    244         final PhoneNumberListAdapter adapter = (PhoneNumberListAdapter) getAdapter();
    245         return adapter.getDataUri(position);
    246     }
    247 
    248     protected String getLookupKey(int position) {
    249         final PhoneNumberListAdapter adapter = (PhoneNumberListAdapter) getAdapter();
    250         return adapter.getLookupKey(position);
    251     }
    252 
    253     @Override
    254     protected void startLoading() {
    255         mLoaderStarted = true;
    256         super.startLoading();
    257     }
    258 
    259     @Override
    260     public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    261         super.onLoadFinished(loader, data);
    262 
    263         // disable scroll bar if there is no data
    264         setVisibleScrollbarEnabled(data != null && !data.isClosed() && data.getCount() > 0);
    265     }
    266 
    267     public void setUseCallableUri(boolean useCallableUri) {
    268         mUseCallableUri = useCallableUri;
    269     }
    270 
    271     public boolean usesCallableUri() {
    272         return mUseCallableUri;
    273     }
    274 
    275     @Override
    276     protected ContactEntryListAdapter createListAdapter() {
    277         PhoneNumberListAdapter adapter = new PhoneNumberListAdapter(getActivity());
    278         adapter.setDisplayPhotos(true);
    279         adapter.setUseCallableUri(mUseCallableUri);
    280         return adapter;
    281     }
    282 
    283     @Override
    284     protected void configureAdapter() {
    285         super.configureAdapter();
    286 
    287         final ContactEntryListAdapter adapter = getAdapter();
    288         if (adapter == null) {
    289             return;
    290         }
    291 
    292         if (!isSearchMode() && mFilter != null) {
    293             adapter.setFilter(mFilter);
    294         }
    295 
    296         setPhotoPosition(adapter);
    297     }
    298 
    299     protected void setPhotoPosition(ContactEntryListAdapter adapter) {
    300         ((PhoneNumberListAdapter) adapter).setPhotoPosition(mPhotoPosition);
    301     }
    302 
    303     @Override
    304     protected View inflateView(LayoutInflater inflater, ViewGroup container) {
    305         return inflater.inflate(R.layout.contact_list_content, null);
    306     }
    307 
    308     public void pickPhoneNumber(Uri uri, boolean isVideoCall) {
    309         if (mShortcutAction == null) {
    310             mListener.onPickDataUri(uri, isVideoCall,
    311                     getCallInitiationType(false /* isRemoteDirectory */));
    312         } else {
    313             startPhoneNumberShortcutIntent(uri, isVideoCall);
    314         }
    315     }
    316 
    317     protected void startPhoneNumberShortcutIntent(Uri uri, boolean isVideoCall) {
    318         ShortcutIntentBuilder builder = new ShortcutIntentBuilder(getActivity(), this);
    319         builder.createPhoneNumberShortcutIntent(uri, mShortcutAction);
    320     }
    321 
    322     @Override
    323     public void onShortcutIntentCreated(Uri uri, Intent shortcutIntent) {
    324         mListener.onShortcutIntentCreated(shortcutIntent);
    325     }
    326 
    327     @Override
    328     public void onPickerResult(Intent data) {
    329         mListener.onPickDataUri(data.getData(), false /* isVideoCall */,
    330                 getCallInitiationType(false /* isRemoteDirectory */));
    331     }
    332 
    333     @Override
    334     public void onActivityResult(int requestCode, int resultCode, Intent data) {
    335         if (requestCode == REQUEST_CODE_ACCOUNT_FILTER) {
    336             if (getActivity() != null) {
    337                 AccountFilterUtil.handleAccountFilterResult(
    338                         ContactListFilterController.getInstance(getActivity()), resultCode, data);
    339             } else {
    340                 Log.e(TAG, "getActivity() returns null during Fragment#onActivityResult()");
    341             }
    342         }
    343     }
    344 
    345     public ContactListFilter getFilter() {
    346         return mFilter;
    347     }
    348 
    349     public void setFilter(ContactListFilter filter) {
    350         if ((mFilter == null && filter == null) ||
    351                 (mFilter != null && mFilter.equals(filter))) {
    352             return;
    353         }
    354 
    355         mFilter = filter;
    356         if (mLoaderStarted) {
    357             reloadData();
    358         }
    359         updateFilterHeaderView();
    360     }
    361 
    362     public void setPhotoPosition(ContactListItemView.PhotoPosition photoPosition) {
    363         mPhotoPosition = photoPosition;
    364 
    365         final PhoneNumberListAdapter adapter = (PhoneNumberListAdapter) getAdapter();
    366         if (adapter != null) {
    367             adapter.setPhotoPosition(photoPosition);
    368         }
    369     }
    370 
    371     /**
    372      * @param isRemoteDirectory {@code true} if the call was initiated using a contact/phone number
    373      *         not in the local contacts database
    374      */
    375     protected int getCallInitiationType(boolean isRemoteDirectory) {
    376         return OnPhoneNumberPickerActionListener.CALL_INITIATION_UNKNOWN;
    377     }
    378 
    379     /**
    380      * Where a lookup key contains analytic event information, logs the associated analytics event.
    381      *
    382      * @param lookupKey The lookup key JSON object.
    383      */
    384     private void maybeTrackAnalytics(String lookupKey) {
    385         try {
    386             JSONObject json = new JSONObject(lookupKey);
    387 
    388             String analyticsCategory = json.getString(
    389                     PhoneNumberListAdapter.PhoneQuery.ANALYTICS_CATEGORY);
    390             String analyticsAction = json.getString(
    391                     PhoneNumberListAdapter.PhoneQuery.ANALYTICS_ACTION);
    392             String analyticsValue = json.getString(
    393                     PhoneNumberListAdapter.PhoneQuery.ANALYTICS_VALUE);
    394 
    395             if (TextUtils.isEmpty(analyticsCategory) || TextUtils.isEmpty(analyticsAction) ||
    396                     TextUtils.isEmpty(analyticsValue)) {
    397                 return;
    398             }
    399 
    400             // Assume that the analytic value being tracked could be a float value, but just cast
    401             // to a long so that the analytic server can handle it.
    402             long value;
    403             try {
    404                 float floatValue = Float.parseFloat(analyticsValue);
    405                 value = (long) floatValue;
    406             } catch (NumberFormatException nfe) {
    407                 return;
    408             }
    409 
    410             AnalyticsUtil.sendEvent(getActivity().getApplication(), analyticsCategory,
    411                     analyticsAction, "" /* label */, value);
    412         } catch (JSONException e) {
    413             // Not an error; just a lookup key that doesn't have the right information.
    414         }
    415     }
    416 }
    417