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