ContactLoaderFragment.java revision 683b57e1fbf27c81c9973de814fc8bb1852e6df8
1/* 2 * Copyright (C) 2011 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.detail; 18 19import com.android.contacts.ContactLoader; 20import com.android.contacts.ContactSaveService; 21import com.android.contacts.R; 22import com.android.contacts.activities.ContactDetailActivity; 23import com.android.contacts.activities.ContactDetailActivity.FragmentKeyListener; 24import com.android.contacts.util.PhoneCapabilityTester; 25import com.android.internal.util.Objects; 26 27import android.app.Activity; 28import android.app.Fragment; 29import android.app.LoaderManager; 30import android.app.LoaderManager.LoaderCallbacks; 31import android.content.ActivityNotFoundException; 32import android.content.ContentValues; 33import android.content.Context; 34import android.content.Intent; 35import android.content.Loader; 36import android.media.RingtoneManager; 37import android.net.Uri; 38import android.os.Bundle; 39import android.provider.ContactsContract.Contacts; 40import android.util.Log; 41import android.view.KeyEvent; 42import android.view.LayoutInflater; 43import android.view.Menu; 44import android.view.MenuInflater; 45import android.view.MenuItem; 46import android.view.View; 47import android.view.ViewGroup; 48import android.widget.Toast; 49 50/** 51 * This is an invisible worker {@link Fragment} that loads the contact details for the contact card. 52 * The data is then passed to the listener, who can then pass the data to other {@link View}s. 53 */ 54public class ContactLoaderFragment extends Fragment implements FragmentKeyListener { 55 56 private static final String TAG = ContactLoaderFragment.class.getSimpleName(); 57 58 /** The launch code when picking a ringtone */ 59 private static final int REQUEST_CODE_PICK_RINGTONE = 1; 60 61 62 private boolean mOptionsMenuOptions; 63 private boolean mOptionsMenuEditable; 64 private boolean mOptionsMenuShareable; 65 private boolean mSendToVoicemailState; 66 private String mCustomRingtone; 67 68 /** 69 * This is a listener to the {@link ContactLoaderFragment} and will be notified when the 70 * contact details have finished loading or if the user selects any menu options. 71 */ 72 public static interface ContactLoaderFragmentListener { 73 /** 74 * Contact was not found, so somehow close this fragment. This is raised after a contact 75 * is removed via Menu/Delete 76 */ 77 public void onContactNotFound(); 78 79 /** 80 * Contact details have finished loading. 81 */ 82 public void onDetailsLoaded(ContactLoader.Result result); 83 84 /** 85 * User decided to go to Edit-Mode 86 */ 87 public void onEditRequested(Uri lookupUri); 88 89 /** 90 * User decided to delete the contact 91 */ 92 public void onDeleteRequested(Uri lookupUri); 93 94 } 95 96 private static final int LOADER_DETAILS = 1; 97 98 private static final String KEY_CONTACT_URI = "contactUri"; 99 private static final String LOADER_ARG_CONTACT_URI = "contactUri"; 100 101 private Context mContext; 102 private Uri mLookupUri; 103 private ContactLoaderFragmentListener mListener; 104 105 private ContactLoader.Result mContactData; 106 107 public ContactLoaderFragment() { 108 } 109 110 @Override 111 public void onCreate(Bundle savedInstanceState) { 112 super.onCreate(savedInstanceState); 113 if (savedInstanceState != null) { 114 mLookupUri = savedInstanceState.getParcelable(KEY_CONTACT_URI); 115 } 116 } 117 118 @Override 119 public void onSaveInstanceState(Bundle outState) { 120 super.onSaveInstanceState(outState); 121 outState.putParcelable(KEY_CONTACT_URI, mLookupUri); 122 } 123 124 @Override 125 public void onAttach(Activity activity) { 126 super.onAttach(activity); 127 mContext = activity; 128 } 129 130 @Override 131 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) { 132 setHasOptionsMenu(true); 133 // This is an invisible view. This fragment is declared in a layout, so it can't be 134 // "viewless". (i.e. can't return null here.) 135 // See also the comment in the layout file. 136 return inflater.inflate(R.layout.contact_detail_loader_fragment, container, false); 137 } 138 139 @Override 140 public void onActivityCreated(Bundle savedInstanceState) { 141 super.onActivityCreated(savedInstanceState); 142 143 if (mLookupUri != null) { 144 Bundle args = new Bundle(); 145 args.putParcelable(LOADER_ARG_CONTACT_URI, mLookupUri); 146 getLoaderManager().initLoader(LOADER_DETAILS, args, mDetailLoaderListener); 147 } 148 } 149 150 public void loadUri(Uri lookupUri) { 151 if (Objects.equal(lookupUri, mLookupUri)) { 152 // Same URI, no need to load the data again 153 return; 154 } 155 156 mLookupUri = lookupUri; 157 if (mLookupUri == null) { 158 getLoaderManager().destroyLoader(LOADER_DETAILS); 159 mContactData = null; 160 if (mListener != null) { 161 mListener.onDetailsLoaded(mContactData); 162 } 163 } else if (getActivity() != null) { 164 Bundle args = new Bundle(); 165 args.putParcelable(LOADER_ARG_CONTACT_URI, mLookupUri); 166 getLoaderManager().restartLoader(LOADER_DETAILS, args, mDetailLoaderListener); 167 } 168 } 169 170 public void setListener(ContactLoaderFragmentListener value) { 171 mListener = value; 172 } 173 174 /** 175 * The listener for the detail loader 176 */ 177 private final LoaderManager.LoaderCallbacks<ContactLoader.Result> mDetailLoaderListener = 178 new LoaderCallbacks<ContactLoader.Result>() { 179 @Override 180 public Loader<ContactLoader.Result> onCreateLoader(int id, Bundle args) { 181 Uri lookupUri = args.getParcelable(LOADER_ARG_CONTACT_URI); 182 return new ContactLoader(mContext, lookupUri, true /* loadGroupMetaData */, 183 true /* loadStreamItems */, false /* load invitable account types */); 184 } 185 186 @Override 187 public void onLoadFinished(Loader<ContactLoader.Result> loader, ContactLoader.Result data) { 188 if (!mLookupUri.equals(data.getUri())) { 189 return; 190 } 191 192 if (data != ContactLoader.Result.NOT_FOUND && data != ContactLoader.Result.ERROR) { 193 mContactData = data; 194 } else { 195 Log.i(TAG, "No contact found: " + ((ContactLoader)loader).getLookupUri()); 196 mContactData = null; 197 } 198 199 if (mListener != null) { 200 if (mContactData == null) { 201 mListener.onContactNotFound(); 202 } else { 203 mListener.onDetailsLoaded(mContactData); 204 } 205 } 206 // Make sure the options menu is setup correctly with the loaded data. 207 getActivity().invalidateOptionsMenu(); 208 } 209 210 @Override 211 public void onLoaderReset(Loader<ContactLoader.Result> loader) { 212 mContactData = null; 213 if (mListener != null) { 214 mListener.onDetailsLoaded(mContactData); 215 } 216 } 217 }; 218 219 220 @Override 221 public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) { 222 inflater.inflate(R.menu.view_contact, menu); 223 } 224 225 public boolean isOptionsMenuChanged() { 226 return mOptionsMenuOptions != isContactOptionsChangeEnabled() 227 || mOptionsMenuEditable != isContactEditable() 228 || mOptionsMenuShareable != isContactShareable(); 229 } 230 231 @Override 232 public void onPrepareOptionsMenu(Menu menu) { 233 mOptionsMenuOptions = isContactOptionsChangeEnabled(); 234 mOptionsMenuEditable = isContactEditable(); 235 mOptionsMenuShareable = isContactShareable(); 236 if (mContactData != null) { 237 mSendToVoicemailState = mContactData.isSendToVoicemail(); 238 mCustomRingtone = mContactData.getCustomRingtone(); 239 } 240 241 // Hide telephony-related settings (ringtone, send to voicemail) 242 // if we don't have a telephone 243 final MenuItem optionsSendToVoicemail = menu.findItem(R.id.menu_send_to_voicemail); 244 if (optionsSendToVoicemail != null) { 245 optionsSendToVoicemail.setChecked(mSendToVoicemailState); 246 optionsSendToVoicemail.setVisible(mOptionsMenuOptions); 247 } 248 final MenuItem optionsRingtone = menu.findItem(R.id.menu_set_ringtone); 249 if (optionsRingtone != null) { 250 optionsRingtone.setVisible(mOptionsMenuOptions); 251 } 252 253 final MenuItem editMenu = menu.findItem(R.id.menu_edit); 254 editMenu.setVisible(mOptionsMenuEditable); 255 256 final MenuItem deleteMenu = menu.findItem(R.id.menu_delete); 257 deleteMenu.setVisible(mOptionsMenuEditable); 258 259 final MenuItem shareMenu = menu.findItem(R.id.menu_share); 260 shareMenu.setVisible(mOptionsMenuShareable); 261 } 262 263 public boolean isContactOptionsChangeEnabled() { 264 return mContactData != null && !mContactData.isDirectoryEntry() 265 && PhoneCapabilityTester.isPhone(mContext); 266 } 267 268 public boolean isContactEditable() { 269 return mContactData != null && !mContactData.isDirectoryEntry(); 270 } 271 272 public boolean isContactShareable() { 273 return mContactData != null && !mContactData.isDirectoryEntry(); 274 } 275 276 @Override 277 public boolean onOptionsItemSelected(MenuItem item) { 278 switch (item.getItemId()) { 279 case R.id.menu_edit: { 280 if (mListener != null) mListener.onEditRequested(mLookupUri); 281 break; 282 } 283 case R.id.menu_delete: { 284 if (mListener != null) mListener.onDeleteRequested(mLookupUri); 285 return true; 286 } 287 case R.id.menu_set_ringtone: { 288 if (mContactData == null) return false; 289 doPickRingtone(); 290 return true; 291 } 292 case R.id.menu_share: { 293 if (mContactData == null) return false; 294 295 final String lookupKey = mContactData.getLookupKey(); 296 final Uri shareUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey); 297 298 final Intent intent = new Intent(Intent.ACTION_SEND); 299 intent.setType(Contacts.CONTENT_VCARD_TYPE); 300 intent.putExtra(Intent.EXTRA_STREAM, shareUri); 301 302 // Launch chooser to share contact via 303 final CharSequence chooseTitle = mContext.getText(R.string.share_via); 304 final Intent chooseIntent = Intent.createChooser(intent, chooseTitle); 305 306 try { 307 mContext.startActivity(chooseIntent); 308 } catch (ActivityNotFoundException ex) { 309 Toast.makeText(mContext, R.string.share_error, Toast.LENGTH_SHORT).show(); 310 } 311 return true; 312 } 313 case R.id.menu_send_to_voicemail: { 314 // Update state and save 315 mSendToVoicemailState = !mSendToVoicemailState; 316 item.setChecked(mSendToVoicemailState); 317 Intent intent = ContactSaveService.createSetSendToVoicemail( 318 mContext, mLookupUri, mSendToVoicemailState); 319 mContext.startService(intent); 320 return true; 321 } 322 } 323 return false; 324 } 325 326 @Override 327 public boolean handleKeyDown(int keyCode) { 328 switch (keyCode) { 329 case KeyEvent.KEYCODE_DEL: { 330 if (mListener != null) mListener.onDeleteRequested(mLookupUri); 331 return true; 332 } 333 } 334 return false; 335 } 336 337 private void doPickRingtone() { 338 339 Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); 340 // Allow user to pick 'Default' 341 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); 342 // Show only ringtones 343 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE); 344 // Don't show 'Silent' 345 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false); 346 347 Uri ringtoneUri; 348 if (mCustomRingtone != null) { 349 ringtoneUri = Uri.parse(mCustomRingtone); 350 } else { 351 // Otherwise pick default ringtone Uri so that something is selected. 352 ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE); 353 } 354 355 // Put checkmark next to the current ringtone for this contact 356 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, ringtoneUri); 357 358 // Launch! 359 startActivityForResult(intent, REQUEST_CODE_PICK_RINGTONE); 360 } 361 362 @Override 363 public void onActivityResult(int requestCode, int resultCode, Intent data) { 364 if (resultCode != Activity.RESULT_OK) { 365 return; 366 } 367 368 switch (requestCode) { 369 case REQUEST_CODE_PICK_RINGTONE: { 370 Uri pickedUri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); 371 handleRingtonePicked(pickedUri); 372 break; 373 } 374 } 375 } 376 377 private void handleRingtonePicked(Uri pickedUri) { 378 if (pickedUri == null || RingtoneManager.isDefault(pickedUri)) { 379 mCustomRingtone = null; 380 } else { 381 mCustomRingtone = pickedUri.toString(); 382 } 383 Intent intent = ContactSaveService.createSetRingtone( 384 mContext, mLookupUri, mCustomRingtone); 385 mContext.startService(intent); 386 } 387} 388