| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| |
| package com.android.contacts.detail; |
| |
| import com.android.contacts.ContactLoader; |
| import com.android.contacts.ContactSaveService; |
| import com.android.contacts.R; |
| import com.android.contacts.activities.ContactDetailActivity.FragmentKeyListener; |
| import com.android.contacts.list.ShortcutIntentBuilder; |
| import com.android.contacts.list.ShortcutIntentBuilder.OnShortcutIntentCreatedListener; |
| import com.android.contacts.util.PhoneCapabilityTester; |
| import com.android.internal.util.Objects; |
| |
| import android.app.Activity; |
| import android.app.Fragment; |
| import android.app.LoaderManager; |
| import android.app.LoaderManager.LoaderCallbacks; |
| import android.content.ActivityNotFoundException; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.Loader; |
| import android.media.RingtoneManager; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.provider.ContactsContract; |
| import android.provider.ContactsContract.Contacts; |
| import android.util.Log; |
| import android.view.KeyEvent; |
| import android.view.LayoutInflater; |
| import android.view.Menu; |
| import android.view.MenuInflater; |
| import android.view.MenuItem; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.Toast; |
| |
| /** |
| * This is an invisible worker {@link Fragment} that loads the contact details for the contact card. |
| * The data is then passed to the listener, who can then pass the data to other {@link View}s. |
| */ |
| public class ContactLoaderFragment extends Fragment implements FragmentKeyListener { |
| |
| private static final String TAG = ContactLoaderFragment.class.getSimpleName(); |
| |
| /** The launch code when picking a ringtone */ |
| private static final int REQUEST_CODE_PICK_RINGTONE = 1; |
| |
| /** This is the Intent action to install a shortcut in the launcher. */ |
| private static final String ACTION_INSTALL_SHORTCUT = |
| "com.android.launcher.action.INSTALL_SHORTCUT"; |
| |
| private boolean mOptionsMenuOptions; |
| private boolean mOptionsMenuEditable; |
| private boolean mOptionsMenuShareable; |
| private boolean mOptionsMenuCanCreateShortcut; |
| private boolean mSendToVoicemailState; |
| private String mCustomRingtone; |
| |
| /** |
| * This is a listener to the {@link ContactLoaderFragment} and will be notified when the |
| * contact details have finished loading or if the user selects any menu options. |
| */ |
| public static interface ContactLoaderFragmentListener { |
| /** |
| * Contact was not found, so somehow close this fragment. This is raised after a contact |
| * is removed via Menu/Delete |
| */ |
| public void onContactNotFound(); |
| |
| /** |
| * Contact details have finished loading. |
| */ |
| public void onDetailsLoaded(ContactLoader.Result result); |
| |
| /** |
| * User decided to go to Edit-Mode |
| */ |
| public void onEditRequested(Uri lookupUri); |
| |
| /** |
| * User decided to delete the contact |
| */ |
| public void onDeleteRequested(Uri lookupUri); |
| |
| } |
| |
| private static final int LOADER_DETAILS = 1; |
| |
| private static final String KEY_CONTACT_URI = "contactUri"; |
| private static final String LOADER_ARG_CONTACT_URI = "contactUri"; |
| |
| private Context mContext; |
| private Uri mLookupUri; |
| private ContactLoaderFragmentListener mListener; |
| |
| private ContactLoader.Result mContactData; |
| |
| public ContactLoaderFragment() { |
| } |
| |
| @Override |
| public void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| if (savedInstanceState != null) { |
| mLookupUri = savedInstanceState.getParcelable(KEY_CONTACT_URI); |
| } |
| } |
| |
| @Override |
| public void onSaveInstanceState(Bundle outState) { |
| super.onSaveInstanceState(outState); |
| outState.putParcelable(KEY_CONTACT_URI, mLookupUri); |
| } |
| |
| @Override |
| public void onAttach(Activity activity) { |
| super.onAttach(activity); |
| mContext = activity; |
| } |
| |
| @Override |
| public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) { |
| setHasOptionsMenu(true); |
| // This is an invisible view. This fragment is declared in a layout, so it can't be |
| // "viewless". (i.e. can't return null here.) |
| // See also the comment in the layout file. |
| return inflater.inflate(R.layout.contact_detail_loader_fragment, container, false); |
| } |
| |
| @Override |
| public void onActivityCreated(Bundle savedInstanceState) { |
| super.onActivityCreated(savedInstanceState); |
| |
| if (mLookupUri != null) { |
| Bundle args = new Bundle(); |
| args.putParcelable(LOADER_ARG_CONTACT_URI, mLookupUri); |
| getLoaderManager().initLoader(LOADER_DETAILS, args, mDetailLoaderListener); |
| } |
| } |
| |
| public void loadUri(Uri lookupUri) { |
| if (Objects.equal(lookupUri, mLookupUri)) { |
| // Same URI, no need to load the data again |
| return; |
| } |
| |
| mLookupUri = lookupUri; |
| if (mLookupUri == null) { |
| getLoaderManager().destroyLoader(LOADER_DETAILS); |
| mContactData = null; |
| if (mListener != null) { |
| mListener.onDetailsLoaded(mContactData); |
| } |
| } else if (getActivity() != null) { |
| Bundle args = new Bundle(); |
| args.putParcelable(LOADER_ARG_CONTACT_URI, mLookupUri); |
| getLoaderManager().restartLoader(LOADER_DETAILS, args, mDetailLoaderListener); |
| } |
| } |
| |
| public void setListener(ContactLoaderFragmentListener value) { |
| mListener = value; |
| } |
| |
| /** |
| * The listener for the detail loader |
| */ |
| private final LoaderManager.LoaderCallbacks<ContactLoader.Result> mDetailLoaderListener = |
| new LoaderCallbacks<ContactLoader.Result>() { |
| @Override |
| public Loader<ContactLoader.Result> onCreateLoader(int id, Bundle args) { |
| Uri lookupUri = args.getParcelable(LOADER_ARG_CONTACT_URI); |
| return new ContactLoader(mContext, lookupUri, true /* loadGroupMetaData */, |
| true /* loadStreamItems */, true /* load invitable account types */, |
| true /* postViewNotification */); |
| } |
| |
| @Override |
| public void onLoadFinished(Loader<ContactLoader.Result> loader, ContactLoader.Result data) { |
| if (!mLookupUri.equals(data.getRequestedUri())) { |
| Log.e(TAG, "Different URI: requested=" + mLookupUri + " actual=" + data); |
| return; |
| } |
| |
| if (data.isError()) { |
| // This shouldn't ever happen, so throw an exception. The {@link ContactLoader} |
| // should log the actual exception. |
| throw new IllegalStateException("Failed to load contact", data.getException()); |
| } else if (data.isNotFound()) { |
| Log.i(TAG, "No contact found: " + ((ContactLoader)loader).getLookupUri()); |
| mContactData = null; |
| } else { |
| mContactData = data; |
| } |
| |
| if (mListener != null) { |
| if (mContactData == null) { |
| mListener.onContactNotFound(); |
| } else { |
| mListener.onDetailsLoaded(mContactData); |
| } |
| } |
| // Make sure the options menu is setup correctly with the loaded data. |
| getActivity().invalidateOptionsMenu(); |
| } |
| |
| @Override |
| public void onLoaderReset(Loader<ContactLoader.Result> loader) {} |
| }; |
| |
| @Override |
| public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) { |
| inflater.inflate(R.menu.view_contact, menu); |
| } |
| |
| public boolean isOptionsMenuChanged() { |
| return mOptionsMenuOptions != isContactOptionsChangeEnabled() |
| || mOptionsMenuEditable != isContactEditable() |
| || mOptionsMenuShareable != isContactShareable() |
| || mOptionsMenuCanCreateShortcut != isContactCanCreateShortcut(); |
| } |
| |
| @Override |
| public void onPrepareOptionsMenu(Menu menu) { |
| mOptionsMenuOptions = isContactOptionsChangeEnabled(); |
| mOptionsMenuEditable = isContactEditable(); |
| mOptionsMenuShareable = isContactShareable(); |
| mOptionsMenuCanCreateShortcut = isContactCanCreateShortcut(); |
| if (mContactData != null) { |
| mSendToVoicemailState = mContactData.isSendToVoicemail(); |
| mCustomRingtone = mContactData.getCustomRingtone(); |
| } |
| |
| // Hide telephony-related settings (ringtone, send to voicemail) |
| // if we don't have a telephone |
| final MenuItem optionsSendToVoicemail = menu.findItem(R.id.menu_send_to_voicemail); |
| if (optionsSendToVoicemail != null) { |
| optionsSendToVoicemail.setChecked(mSendToVoicemailState); |
| optionsSendToVoicemail.setVisible(mOptionsMenuOptions); |
| } |
| final MenuItem optionsRingtone = menu.findItem(R.id.menu_set_ringtone); |
| if (optionsRingtone != null) { |
| optionsRingtone.setVisible(mOptionsMenuOptions); |
| } |
| |
| final MenuItem editMenu = menu.findItem(R.id.menu_edit); |
| editMenu.setVisible(mOptionsMenuEditable); |
| |
| final MenuItem deleteMenu = menu.findItem(R.id.menu_delete); |
| deleteMenu.setVisible(mOptionsMenuEditable); |
| |
| final MenuItem shareMenu = menu.findItem(R.id.menu_share); |
| shareMenu.setVisible(mOptionsMenuShareable); |
| |
| final MenuItem createContactShortcutMenu = menu.findItem(R.id.menu_create_contact_shortcut); |
| createContactShortcutMenu.setVisible(mOptionsMenuCanCreateShortcut); |
| } |
| |
| public boolean isContactOptionsChangeEnabled() { |
| return mContactData != null && !mContactData.isDirectoryEntry() |
| && PhoneCapabilityTester.isPhone(mContext); |
| } |
| |
| public boolean isContactEditable() { |
| return mContactData != null && !mContactData.isDirectoryEntry(); |
| } |
| |
| public boolean isContactShareable() { |
| return mContactData != null && !mContactData.isDirectoryEntry(); |
| } |
| |
| public boolean isContactCanCreateShortcut() { |
| return mContactData != null && !mContactData.isUserProfile() |
| && !mContactData.isDirectoryEntry(); |
| } |
| |
| @Override |
| public boolean onOptionsItemSelected(MenuItem item) { |
| switch (item.getItemId()) { |
| case R.id.menu_edit: { |
| if (mListener != null) mListener.onEditRequested(mLookupUri); |
| break; |
| } |
| case R.id.menu_delete: { |
| if (mListener != null) mListener.onDeleteRequested(mLookupUri); |
| return true; |
| } |
| case R.id.menu_set_ringtone: { |
| if (mContactData == null) return false; |
| doPickRingtone(); |
| return true; |
| } |
| case R.id.menu_share: { |
| if (mContactData == null) return false; |
| |
| final String lookupKey = mContactData.getLookupKey(); |
| Uri shareUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey); |
| if (mContactData.isUserProfile()) { |
| // User is sharing the profile. We don't want to force the receiver to have |
| // the highly-privileged READ_PROFILE permission, so we need to request a |
| // pre-authorized URI from the provider. |
| shareUri = getPreAuthorizedUri(shareUri); |
| } |
| |
| final Intent intent = new Intent(Intent.ACTION_SEND); |
| intent.setType(Contacts.CONTENT_VCARD_TYPE); |
| intent.putExtra(Intent.EXTRA_STREAM, shareUri); |
| |
| // Launch chooser to share contact via |
| final CharSequence chooseTitle = mContext.getText(R.string.share_via); |
| final Intent chooseIntent = Intent.createChooser(intent, chooseTitle); |
| |
| try { |
| mContext.startActivity(chooseIntent); |
| } catch (ActivityNotFoundException ex) { |
| Toast.makeText(mContext, R.string.share_error, Toast.LENGTH_SHORT).show(); |
| } |
| return true; |
| } |
| case R.id.menu_send_to_voicemail: { |
| // Update state and save |
| mSendToVoicemailState = !mSendToVoicemailState; |
| item.setChecked(mSendToVoicemailState); |
| Intent intent = ContactSaveService.createSetSendToVoicemail( |
| mContext, mLookupUri, mSendToVoicemailState); |
| mContext.startService(intent); |
| return true; |
| } |
| case R.id.menu_create_contact_shortcut: { |
| // Create a launcher shortcut with this contact |
| createLauncherShortcutWithContact(); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Creates a launcher shortcut with the current contact. |
| */ |
| private void createLauncherShortcutWithContact() { |
| // Hold the parent activity of this fragment in case this fragment is destroyed |
| // before the callback to onShortcutIntentCreated(...) |
| final Activity parentActivity = getActivity(); |
| |
| ShortcutIntentBuilder builder = new ShortcutIntentBuilder(parentActivity, |
| new OnShortcutIntentCreatedListener() { |
| |
| @Override |
| public void onShortcutIntentCreated(Uri uri, Intent shortcutIntent) { |
| // Broadcast the shortcutIntent to the launcher to create a |
| // shortcut to this contact |
| shortcutIntent.setAction(ACTION_INSTALL_SHORTCUT); |
| parentActivity.sendBroadcast(shortcutIntent); |
| |
| // Send a toast to give feedback to the user that a shortcut to this |
| // contact was added to the launcher. |
| Toast.makeText(parentActivity, |
| R.string.createContactShortcutSuccessful, |
| Toast.LENGTH_SHORT).show(); |
| } |
| |
| }); |
| builder.createContactShortcutIntent(mLookupUri); |
| } |
| |
| /** |
| * Calls into the contacts provider to get a pre-authorized version of the given URI. |
| */ |
| private Uri getPreAuthorizedUri(Uri uri) { |
| Bundle uriBundle = new Bundle(); |
| uriBundle.putParcelable(ContactsContract.Authorization.KEY_URI_TO_AUTHORIZE, uri); |
| Bundle authResponse = mContext.getContentResolver().call( |
| ContactsContract.AUTHORITY_URI, |
| ContactsContract.Authorization.AUTHORIZATION_METHOD, |
| null, |
| uriBundle); |
| if (authResponse != null) { |
| return (Uri) authResponse.getParcelable( |
| ContactsContract.Authorization.KEY_AUTHORIZED_URI); |
| } else { |
| return uri; |
| } |
| } |
| |
| @Override |
| public boolean handleKeyDown(int keyCode) { |
| switch (keyCode) { |
| case KeyEvent.KEYCODE_DEL: { |
| if (mListener != null) mListener.onDeleteRequested(mLookupUri); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void doPickRingtone() { |
| |
| Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); |
| // Allow user to pick 'Default' |
| intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); |
| // Show only ringtones |
| intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE); |
| // Don't show 'Silent' |
| intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false); |
| |
| Uri ringtoneUri; |
| if (mCustomRingtone != null) { |
| ringtoneUri = Uri.parse(mCustomRingtone); |
| } else { |
| // Otherwise pick default ringtone Uri so that something is selected. |
| ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE); |
| } |
| |
| // Put checkmark next to the current ringtone for this contact |
| intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, ringtoneUri); |
| |
| // Launch! |
| startActivityForResult(intent, REQUEST_CODE_PICK_RINGTONE); |
| } |
| |
| @Override |
| public void onActivityResult(int requestCode, int resultCode, Intent data) { |
| if (resultCode != Activity.RESULT_OK) { |
| return; |
| } |
| |
| switch (requestCode) { |
| case REQUEST_CODE_PICK_RINGTONE: { |
| Uri pickedUri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); |
| handleRingtonePicked(pickedUri); |
| break; |
| } |
| } |
| } |
| |
| private void handleRingtonePicked(Uri pickedUri) { |
| if (pickedUri == null || RingtoneManager.isDefault(pickedUri)) { |
| mCustomRingtone = null; |
| } else { |
| mCustomRingtone = pickedUri.toString(); |
| } |
| Intent intent = ContactSaveService.createSetRingtone( |
| mContext, mLookupUri, mCustomRingtone); |
| mContext.startService(intent); |
| } |
| |
| /** Toggles whether to load stream items. Just for debugging */ |
| public void toggleLoadStreamItems() { |
| Loader<ContactLoader.Result> loaderObj = getLoaderManager().getLoader(LOADER_DETAILS); |
| ContactLoader loader = (ContactLoader) loaderObj; |
| loader.setLoadStreamItems(!loader.getLoadStreamItems()); |
| } |
| |
| /** Returns whether to load stream items. Just for debugging */ |
| public boolean getLoadStreamItems() { |
| Loader<ContactLoader.Result> loaderObj = getLoaderManager().getLoader(LOADER_DETAILS); |
| ContactLoader loader = (ContactLoader) loaderObj; |
| return loader != null && loader.getLoadStreamItems(); |
| } |
| } |