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