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