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