Home | History | Annotate | Download | only in detail
      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 
     17 package com.android.contacts.detail;
     18 
     19 import com.android.contacts.ContactLoader;
     20 import com.android.contacts.ContactSaveService;
     21 import com.android.contacts.R;
     22 import com.android.contacts.activities.ContactDetailActivity.FragmentKeyListener;
     23 import com.android.contacts.util.PhoneCapabilityTester;
     24 import com.android.internal.util.Objects;
     25 
     26 import android.app.Activity;
     27 import android.app.Fragment;
     28 import android.app.LoaderManager;
     29 import android.app.LoaderManager.LoaderCallbacks;
     30 import android.content.ActivityNotFoundException;
     31 import android.content.Context;
     32 import android.content.Intent;
     33 import android.content.Loader;
     34 import android.media.RingtoneManager;
     35 import android.net.Uri;
     36 import android.os.AsyncTask;
     37 import android.os.Bundle;
     38 import android.provider.ContactsContract;
     39 import android.provider.ContactsContract.Contacts;
     40 import android.util.Log;
     41 import android.view.KeyEvent;
     42 import android.view.LayoutInflater;
     43 import android.view.Menu;
     44 import android.view.MenuInflater;
     45 import android.view.MenuItem;
     46 import android.view.View;
     47 import android.view.ViewGroup;
     48 import 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  */
     54 public 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 */, true /* load invitable account types */);
    184         }
    185 
    186         @Override
    187         public void onLoadFinished(Loader<ContactLoader.Result> loader, ContactLoader.Result data) {
    188             if (!mLookupUri.equals(data.getRequestedUri())) {
    189                 return;
    190             }
    191 
    192             if (data.isError()) {
    193                 // This shouldn't ever happen, so throw an exception. The {@link ContactLoader}
    194                 // should log the actual exception.
    195                 throw new IllegalStateException("Failed to load contact", data.getException());
    196             } else if (data.isNotFound()) {
    197                 Log.i(TAG, "No contact found: " + ((ContactLoader)loader).getLookupUri());
    198                 mContactData = null;
    199             } else {
    200                 mContactData = data;
    201             }
    202 
    203             if (mListener != null) {
    204                 if (mContactData == null) {
    205                     mListener.onContactNotFound();
    206                 } else {
    207                     mListener.onDetailsLoaded(mContactData);
    208                 }
    209             }
    210             // Make sure the options menu is setup correctly with the loaded data.
    211             getActivity().invalidateOptionsMenu();
    212         }
    213 
    214         @Override
    215         public void onLoaderReset(Loader<ContactLoader.Result> loader) {}
    216     };
    217 
    218     @Override
    219     public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
    220         inflater.inflate(R.menu.view_contact, menu);
    221     }
    222 
    223     public boolean isOptionsMenuChanged() {
    224         return mOptionsMenuOptions != isContactOptionsChangeEnabled()
    225                 || mOptionsMenuEditable != isContactEditable()
    226                 || mOptionsMenuShareable != isContactShareable();
    227     }
    228 
    229     @Override
    230     public void onPrepareOptionsMenu(Menu menu) {
    231         mOptionsMenuOptions = isContactOptionsChangeEnabled();
    232         mOptionsMenuEditable = isContactEditable();
    233         mOptionsMenuShareable = isContactShareable();
    234         if (mContactData != null) {
    235             mSendToVoicemailState = mContactData.isSendToVoicemail();
    236             mCustomRingtone = mContactData.getCustomRingtone();
    237         }
    238 
    239         // Hide telephony-related settings (ringtone, send to voicemail)
    240         // if we don't have a telephone
    241         final MenuItem optionsSendToVoicemail = menu.findItem(R.id.menu_send_to_voicemail);
    242         if (optionsSendToVoicemail != null) {
    243             optionsSendToVoicemail.setChecked(mSendToVoicemailState);
    244             optionsSendToVoicemail.setVisible(mOptionsMenuOptions);
    245         }
    246         final MenuItem optionsRingtone = menu.findItem(R.id.menu_set_ringtone);
    247         if (optionsRingtone != null) {
    248             optionsRingtone.setVisible(mOptionsMenuOptions);
    249         }
    250 
    251         final MenuItem editMenu = menu.findItem(R.id.menu_edit);
    252         editMenu.setVisible(mOptionsMenuEditable);
    253 
    254         final MenuItem deleteMenu = menu.findItem(R.id.menu_delete);
    255         deleteMenu.setVisible(mOptionsMenuEditable);
    256 
    257         final MenuItem shareMenu = menu.findItem(R.id.menu_share);
    258         shareMenu.setVisible(mOptionsMenuShareable);
    259     }
    260 
    261     public boolean isContactOptionsChangeEnabled() {
    262         return mContactData != null && !mContactData.isDirectoryEntry()
    263                 && PhoneCapabilityTester.isPhone(mContext);
    264     }
    265 
    266     public boolean isContactEditable() {
    267         return mContactData != null && !mContactData.isDirectoryEntry();
    268     }
    269 
    270     public boolean isContactShareable() {
    271         return mContactData != null && !mContactData.isDirectoryEntry();
    272     }
    273 
    274     @Override
    275     public boolean onOptionsItemSelected(MenuItem item) {
    276         switch (item.getItemId()) {
    277             case R.id.menu_edit: {
    278                 if (mListener != null) mListener.onEditRequested(mLookupUri);
    279                 break;
    280             }
    281             case R.id.menu_delete: {
    282                 if (mListener != null) mListener.onDeleteRequested(mLookupUri);
    283                 return true;
    284             }
    285             case R.id.menu_set_ringtone: {
    286                 if (mContactData == null) return false;
    287                 doPickRingtone();
    288                 return true;
    289             }
    290             case R.id.menu_share: {
    291                 if (mContactData == null) return false;
    292 
    293                 final String lookupKey = mContactData.getLookupKey();
    294                 Uri shareUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey);
    295                 if (mContactData.isUserProfile()) {
    296                     // User is sharing the profile.  We don't want to force the receiver to have
    297                     // the highly-privileged READ_PROFILE permission, so we need to request a
    298                     // pre-authorized URI from the provider.
    299                     shareUri = getPreAuthorizedUri(shareUri);
    300                 }
    301 
    302                 final Intent intent = new Intent(Intent.ACTION_SEND);
    303                 intent.setType(Contacts.CONTENT_VCARD_TYPE);
    304                 intent.putExtra(Intent.EXTRA_STREAM, shareUri);
    305 
    306                 // Launch chooser to share contact via
    307                 final CharSequence chooseTitle = mContext.getText(R.string.share_via);
    308                 final Intent chooseIntent = Intent.createChooser(intent, chooseTitle);
    309 
    310                 try {
    311                     mContext.startActivity(chooseIntent);
    312                 } catch (ActivityNotFoundException ex) {
    313                     Toast.makeText(mContext, R.string.share_error, Toast.LENGTH_SHORT).show();
    314                 }
    315                 return true;
    316             }
    317             case R.id.menu_send_to_voicemail: {
    318                 // Update state and save
    319                 mSendToVoicemailState = !mSendToVoicemailState;
    320                 item.setChecked(mSendToVoicemailState);
    321                 Intent intent = ContactSaveService.createSetSendToVoicemail(
    322                         mContext, mLookupUri, mSendToVoicemailState);
    323                 mContext.startService(intent);
    324                 return true;
    325             }
    326         }
    327         return false;
    328     }
    329 
    330     /**
    331      * Calls into the contacts provider to get a pre-authorized version of the given URI.
    332      */
    333     private Uri getPreAuthorizedUri(Uri uri) {
    334         Bundle uriBundle = new Bundle();
    335         uriBundle.putParcelable(ContactsContract.Authorization.KEY_URI_TO_AUTHORIZE, uri);
    336         Bundle authResponse = mContext.getContentResolver().call(
    337                 ContactsContract.AUTHORITY_URI,
    338                 ContactsContract.Authorization.AUTHORIZATION_METHOD,
    339                 null,
    340                 uriBundle);
    341         if (authResponse != null) {
    342             return (Uri) authResponse.getParcelable(
    343                     ContactsContract.Authorization.KEY_AUTHORIZED_URI);
    344         } else {
    345             return uri;
    346         }
    347     }
    348 
    349     @Override
    350     public boolean handleKeyDown(int keyCode) {
    351         switch (keyCode) {
    352             case KeyEvent.KEYCODE_DEL: {
    353                 if (mListener != null) mListener.onDeleteRequested(mLookupUri);
    354                 return true;
    355             }
    356         }
    357         return false;
    358     }
    359 
    360     private void doPickRingtone() {
    361 
    362         Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
    363         // Allow user to pick 'Default'
    364         intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
    365         // Show only ringtones
    366         intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE);
    367         // Don't show 'Silent'
    368         intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false);
    369 
    370         Uri ringtoneUri;
    371         if (mCustomRingtone != null) {
    372             ringtoneUri = Uri.parse(mCustomRingtone);
    373         } else {
    374             // Otherwise pick default ringtone Uri so that something is selected.
    375             ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
    376         }
    377 
    378         // Put checkmark next to the current ringtone for this contact
    379         intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, ringtoneUri);
    380 
    381         // Launch!
    382         startActivityForResult(intent, REQUEST_CODE_PICK_RINGTONE);
    383     }
    384 
    385     @Override
    386     public void onActivityResult(int requestCode, int resultCode, Intent data) {
    387         if (resultCode != Activity.RESULT_OK) {
    388             return;
    389         }
    390 
    391         switch (requestCode) {
    392             case REQUEST_CODE_PICK_RINGTONE: {
    393                 Uri pickedUri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
    394                 handleRingtonePicked(pickedUri);
    395                 break;
    396             }
    397         }
    398     }
    399 
    400     private void handleRingtonePicked(Uri pickedUri) {
    401         if (pickedUri == null || RingtoneManager.isDefault(pickedUri)) {
    402             mCustomRingtone = null;
    403         } else {
    404             mCustomRingtone = pickedUri.toString();
    405         }
    406         Intent intent = ContactSaveService.createSetRingtone(
    407                 mContext, mLookupUri, mCustomRingtone);
    408         mContext.startService(intent);
    409     }
    410 }
    411