• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.list.ShortcutIntentBuilder;
24 import com.android.contacts.list.ShortcutIntentBuilder.OnShortcutIntentCreatedListener;
25 import com.android.contacts.util.PhoneCapabilityTester;
26 import com.android.internal.util.Objects;
27 
28 import android.app.Activity;
29 import android.app.Fragment;
30 import android.app.LoaderManager;
31 import android.app.LoaderManager.LoaderCallbacks;
32 import android.content.ActivityNotFoundException;
33 import android.content.Context;
34 import android.content.Intent;
35 import android.content.Loader;
36 import android.media.RingtoneManager;
37 import android.net.Uri;
38 import android.os.Bundle;
39 import android.provider.ContactsContract;
40 import android.provider.ContactsContract.Contacts;
41 import android.util.Log;
42 import android.view.KeyEvent;
43 import android.view.LayoutInflater;
44 import android.view.Menu;
45 import android.view.MenuInflater;
46 import android.view.MenuItem;
47 import android.view.View;
48 import android.view.ViewGroup;
49 import android.widget.Toast;
50 
51 /**
52  * This is an invisible worker {@link Fragment} that loads the contact details for the contact card.
53  * The data is then passed to the listener, who can then pass the data to other {@link View}s.
54  */
55 public class ContactLoaderFragment extends Fragment implements FragmentKeyListener {
56 
57     private static final String TAG = ContactLoaderFragment.class.getSimpleName();
58 
59     /** The launch code when picking a ringtone */
60     private static final int REQUEST_CODE_PICK_RINGTONE = 1;
61 
62     /** This is the Intent action to install a shortcut in the launcher. */
63     private static final String ACTION_INSTALL_SHORTCUT =
64             "com.android.launcher.action.INSTALL_SHORTCUT";
65 
66     private boolean mOptionsMenuOptions;
67     private boolean mOptionsMenuEditable;
68     private boolean mOptionsMenuShareable;
69     private boolean mOptionsMenuCanCreateShortcut;
70     private boolean mSendToVoicemailState;
71     private String mCustomRingtone;
72 
73     /**
74      * This is a listener to the {@link ContactLoaderFragment} and will be notified when the
75      * contact details have finished loading or if the user selects any menu options.
76      */
77     public static interface ContactLoaderFragmentListener {
78         /**
79          * Contact was not found, so somehow close this fragment. This is raised after a contact
80          * is removed via Menu/Delete
81          */
onContactNotFound()82         public void onContactNotFound();
83 
84         /**
85          * Contact details have finished loading.
86          */
onDetailsLoaded(ContactLoader.Result result)87         public void onDetailsLoaded(ContactLoader.Result result);
88 
89         /**
90          * User decided to go to Edit-Mode
91          */
onEditRequested(Uri lookupUri)92         public void onEditRequested(Uri lookupUri);
93 
94         /**
95          * User decided to delete the contact
96          */
onDeleteRequested(Uri lookupUri)97         public void onDeleteRequested(Uri lookupUri);
98 
99     }
100 
101     private static final int LOADER_DETAILS = 1;
102 
103     private static final String KEY_CONTACT_URI = "contactUri";
104     private static final String LOADER_ARG_CONTACT_URI = "contactUri";
105 
106     private Context mContext;
107     private Uri mLookupUri;
108     private ContactLoaderFragmentListener mListener;
109 
110     private ContactLoader.Result mContactData;
111 
ContactLoaderFragment()112     public ContactLoaderFragment() {
113     }
114 
115     @Override
onCreate(Bundle savedInstanceState)116     public void onCreate(Bundle savedInstanceState) {
117         super.onCreate(savedInstanceState);
118         if (savedInstanceState != null) {
119             mLookupUri = savedInstanceState.getParcelable(KEY_CONTACT_URI);
120         }
121     }
122 
123     @Override
onSaveInstanceState(Bundle outState)124     public void onSaveInstanceState(Bundle outState) {
125         super.onSaveInstanceState(outState);
126         outState.putParcelable(KEY_CONTACT_URI, mLookupUri);
127     }
128 
129     @Override
onAttach(Activity activity)130     public void onAttach(Activity activity) {
131         super.onAttach(activity);
132         mContext = activity;
133     }
134 
135     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState)136     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
137         setHasOptionsMenu(true);
138         // This is an invisible view.  This fragment is declared in a layout, so it can't be
139         // "viewless".  (i.e. can't return null here.)
140         // See also the comment in the layout file.
141         return inflater.inflate(R.layout.contact_detail_loader_fragment, container, false);
142     }
143 
144     @Override
onActivityCreated(Bundle savedInstanceState)145     public void onActivityCreated(Bundle savedInstanceState) {
146         super.onActivityCreated(savedInstanceState);
147 
148         if (mLookupUri != null) {
149             Bundle args = new Bundle();
150             args.putParcelable(LOADER_ARG_CONTACT_URI, mLookupUri);
151             getLoaderManager().initLoader(LOADER_DETAILS, args, mDetailLoaderListener);
152         }
153     }
154 
loadUri(Uri lookupUri)155     public void loadUri(Uri lookupUri) {
156         if (Objects.equal(lookupUri, mLookupUri)) {
157             // Same URI, no need to load the data again
158             return;
159         }
160 
161         mLookupUri = lookupUri;
162         if (mLookupUri == null) {
163             getLoaderManager().destroyLoader(LOADER_DETAILS);
164             mContactData = null;
165             if (mListener != null) {
166                 mListener.onDetailsLoaded(mContactData);
167             }
168         } else if (getActivity() != null) {
169             Bundle args = new Bundle();
170             args.putParcelable(LOADER_ARG_CONTACT_URI, mLookupUri);
171             getLoaderManager().restartLoader(LOADER_DETAILS, args, mDetailLoaderListener);
172         }
173     }
174 
setListener(ContactLoaderFragmentListener value)175     public void setListener(ContactLoaderFragmentListener value) {
176         mListener = value;
177     }
178 
179     /**
180      * The listener for the detail loader
181      */
182     private final LoaderManager.LoaderCallbacks<ContactLoader.Result> mDetailLoaderListener =
183             new LoaderCallbacks<ContactLoader.Result>() {
184         @Override
185         public Loader<ContactLoader.Result> onCreateLoader(int id, Bundle args) {
186             Uri lookupUri = args.getParcelable(LOADER_ARG_CONTACT_URI);
187             return new ContactLoader(mContext, lookupUri, true /* loadGroupMetaData */,
188                     true /* loadStreamItems */, true /* load invitable account types */,
189                     true /* postViewNotification */);
190         }
191 
192         @Override
193         public void onLoadFinished(Loader<ContactLoader.Result> loader, ContactLoader.Result data) {
194             if (!mLookupUri.equals(data.getRequestedUri())) {
195                 Log.e(TAG, "Different URI: requested=" + mLookupUri + "  actual=" + data);
196                 return;
197             }
198 
199             if (data.isError()) {
200                 // This shouldn't ever happen, so throw an exception. The {@link ContactLoader}
201                 // should log the actual exception.
202                 throw new IllegalStateException("Failed to load contact", data.getException());
203             } else if (data.isNotFound()) {
204                 Log.i(TAG, "No contact found: " + ((ContactLoader)loader).getLookupUri());
205                 mContactData = null;
206             } else {
207                 mContactData = data;
208             }
209 
210             if (mListener != null) {
211                 if (mContactData == null) {
212                     mListener.onContactNotFound();
213                 } else {
214                     mListener.onDetailsLoaded(mContactData);
215                 }
216             }
217             // Make sure the options menu is setup correctly with the loaded data.
218             getActivity().invalidateOptionsMenu();
219         }
220 
221         @Override
222         public void onLoaderReset(Loader<ContactLoader.Result> loader) {}
223     };
224 
225     @Override
onCreateOptionsMenu(Menu menu, final MenuInflater inflater)226     public void onCreateOptionsMenu(Menu menu, final MenuInflater inflater) {
227         inflater.inflate(R.menu.view_contact, menu);
228     }
229 
isOptionsMenuChanged()230     public boolean isOptionsMenuChanged() {
231         return mOptionsMenuOptions != isContactOptionsChangeEnabled()
232                 || mOptionsMenuEditable != isContactEditable()
233                 || mOptionsMenuShareable != isContactShareable()
234                 || mOptionsMenuCanCreateShortcut != isContactCanCreateShortcut();
235     }
236 
237     @Override
onPrepareOptionsMenu(Menu menu)238     public void onPrepareOptionsMenu(Menu menu) {
239         mOptionsMenuOptions = isContactOptionsChangeEnabled();
240         mOptionsMenuEditable = isContactEditable();
241         mOptionsMenuShareable = isContactShareable();
242         mOptionsMenuCanCreateShortcut = isContactCanCreateShortcut();
243         if (mContactData != null) {
244             mSendToVoicemailState = mContactData.isSendToVoicemail();
245             mCustomRingtone = mContactData.getCustomRingtone();
246         }
247 
248         // Hide telephony-related settings (ringtone, send to voicemail)
249         // if we don't have a telephone
250         final MenuItem optionsSendToVoicemail = menu.findItem(R.id.menu_send_to_voicemail);
251         if (optionsSendToVoicemail != null) {
252             optionsSendToVoicemail.setChecked(mSendToVoicemailState);
253             optionsSendToVoicemail.setVisible(mOptionsMenuOptions);
254         }
255         final MenuItem optionsRingtone = menu.findItem(R.id.menu_set_ringtone);
256         if (optionsRingtone != null) {
257             optionsRingtone.setVisible(mOptionsMenuOptions);
258         }
259 
260         final MenuItem editMenu = menu.findItem(R.id.menu_edit);
261         editMenu.setVisible(mOptionsMenuEditable);
262 
263         final MenuItem deleteMenu = menu.findItem(R.id.menu_delete);
264         deleteMenu.setVisible(mOptionsMenuEditable);
265 
266         final MenuItem shareMenu = menu.findItem(R.id.menu_share);
267         shareMenu.setVisible(mOptionsMenuShareable);
268 
269         final MenuItem createContactShortcutMenu = menu.findItem(R.id.menu_create_contact_shortcut);
270         createContactShortcutMenu.setVisible(mOptionsMenuCanCreateShortcut);
271     }
272 
isContactOptionsChangeEnabled()273     public boolean isContactOptionsChangeEnabled() {
274         return mContactData != null && !mContactData.isDirectoryEntry()
275                 && PhoneCapabilityTester.isPhone(mContext);
276     }
277 
isContactEditable()278     public boolean isContactEditable() {
279         return mContactData != null && !mContactData.isDirectoryEntry();
280     }
281 
isContactShareable()282     public boolean isContactShareable() {
283         return mContactData != null && !mContactData.isDirectoryEntry();
284     }
285 
isContactCanCreateShortcut()286     public boolean isContactCanCreateShortcut() {
287         return mContactData != null && !mContactData.isUserProfile()
288                 && !mContactData.isDirectoryEntry();
289     }
290 
291     @Override
onOptionsItemSelected(MenuItem item)292     public boolean onOptionsItemSelected(MenuItem item) {
293         switch (item.getItemId()) {
294             case R.id.menu_edit: {
295                 if (mListener != null) mListener.onEditRequested(mLookupUri);
296                 break;
297             }
298             case R.id.menu_delete: {
299                 if (mListener != null) mListener.onDeleteRequested(mLookupUri);
300                 return true;
301             }
302             case R.id.menu_set_ringtone: {
303                 if (mContactData == null) return false;
304                 doPickRingtone();
305                 return true;
306             }
307             case R.id.menu_share: {
308                 if (mContactData == null) return false;
309 
310                 final String lookupKey = mContactData.getLookupKey();
311                 Uri shareUri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey);
312                 if (mContactData.isUserProfile()) {
313                     // User is sharing the profile.  We don't want to force the receiver to have
314                     // the highly-privileged READ_PROFILE permission, so we need to request a
315                     // pre-authorized URI from the provider.
316                     shareUri = getPreAuthorizedUri(shareUri);
317                 }
318 
319                 final Intent intent = new Intent(Intent.ACTION_SEND);
320                 intent.setType(Contacts.CONTENT_VCARD_TYPE);
321                 intent.putExtra(Intent.EXTRA_STREAM, shareUri);
322 
323                 // Launch chooser to share contact via
324                 final CharSequence chooseTitle = mContext.getText(R.string.share_via);
325                 final Intent chooseIntent = Intent.createChooser(intent, chooseTitle);
326 
327                 try {
328                     mContext.startActivity(chooseIntent);
329                 } catch (ActivityNotFoundException ex) {
330                     Toast.makeText(mContext, R.string.share_error, Toast.LENGTH_SHORT).show();
331                 }
332                 return true;
333             }
334             case R.id.menu_send_to_voicemail: {
335                 // Update state and save
336                 mSendToVoicemailState = !mSendToVoicemailState;
337                 item.setChecked(mSendToVoicemailState);
338                 Intent intent = ContactSaveService.createSetSendToVoicemail(
339                         mContext, mLookupUri, mSendToVoicemailState);
340                 mContext.startService(intent);
341                 return true;
342             }
343             case R.id.menu_create_contact_shortcut: {
344                 // Create a launcher shortcut with this contact
345                 createLauncherShortcutWithContact();
346                 return true;
347             }
348         }
349         return false;
350     }
351 
352     /**
353      * Creates a launcher shortcut with the current contact.
354      */
createLauncherShortcutWithContact()355     private void createLauncherShortcutWithContact() {
356         // Hold the parent activity of this fragment in case this fragment is destroyed
357         // before the callback to onShortcutIntentCreated(...)
358         final Activity parentActivity = getActivity();
359 
360         ShortcutIntentBuilder builder = new ShortcutIntentBuilder(parentActivity,
361                 new OnShortcutIntentCreatedListener() {
362 
363             @Override
364             public void onShortcutIntentCreated(Uri uri, Intent shortcutIntent) {
365                 // Broadcast the shortcutIntent to the launcher to create a
366                 // shortcut to this contact
367                 shortcutIntent.setAction(ACTION_INSTALL_SHORTCUT);
368                 parentActivity.sendBroadcast(shortcutIntent);
369 
370                 // Send a toast to give feedback to the user that a shortcut to this
371                 // contact was added to the launcher.
372                 Toast.makeText(parentActivity,
373                         R.string.createContactShortcutSuccessful,
374                         Toast.LENGTH_SHORT).show();
375             }
376 
377         });
378         builder.createContactShortcutIntent(mLookupUri);
379     }
380 
381     /**
382      * Calls into the contacts provider to get a pre-authorized version of the given URI.
383      */
getPreAuthorizedUri(Uri uri)384     private Uri getPreAuthorizedUri(Uri uri) {
385         Bundle uriBundle = new Bundle();
386         uriBundle.putParcelable(ContactsContract.Authorization.KEY_URI_TO_AUTHORIZE, uri);
387         Bundle authResponse = mContext.getContentResolver().call(
388                 ContactsContract.AUTHORITY_URI,
389                 ContactsContract.Authorization.AUTHORIZATION_METHOD,
390                 null,
391                 uriBundle);
392         if (authResponse != null) {
393             return (Uri) authResponse.getParcelable(
394                     ContactsContract.Authorization.KEY_AUTHORIZED_URI);
395         } else {
396             return uri;
397         }
398     }
399 
400     @Override
handleKeyDown(int keyCode)401     public boolean handleKeyDown(int keyCode) {
402         switch (keyCode) {
403             case KeyEvent.KEYCODE_DEL: {
404                 if (mListener != null) mListener.onDeleteRequested(mLookupUri);
405                 return true;
406             }
407         }
408         return false;
409     }
410 
doPickRingtone()411     private void doPickRingtone() {
412 
413         Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
414         // Allow user to pick 'Default'
415         intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
416         // Show only ringtones
417         intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_RINGTONE);
418         // Don't show 'Silent'
419         intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false);
420 
421         Uri ringtoneUri;
422         if (mCustomRingtone != null) {
423             ringtoneUri = Uri.parse(mCustomRingtone);
424         } else {
425             // Otherwise pick default ringtone Uri so that something is selected.
426             ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
427         }
428 
429         // Put checkmark next to the current ringtone for this contact
430         intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, ringtoneUri);
431 
432         // Launch!
433         startActivityForResult(intent, REQUEST_CODE_PICK_RINGTONE);
434     }
435 
436     @Override
onActivityResult(int requestCode, int resultCode, Intent data)437     public void onActivityResult(int requestCode, int resultCode, Intent data) {
438         if (resultCode != Activity.RESULT_OK) {
439             return;
440         }
441 
442         switch (requestCode) {
443             case REQUEST_CODE_PICK_RINGTONE: {
444                 Uri pickedUri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
445                 handleRingtonePicked(pickedUri);
446                 break;
447             }
448         }
449     }
450 
handleRingtonePicked(Uri pickedUri)451     private void handleRingtonePicked(Uri pickedUri) {
452         if (pickedUri == null || RingtoneManager.isDefault(pickedUri)) {
453             mCustomRingtone = null;
454         } else {
455             mCustomRingtone = pickedUri.toString();
456         }
457         Intent intent = ContactSaveService.createSetRingtone(
458                 mContext, mLookupUri, mCustomRingtone);
459         mContext.startService(intent);
460     }
461 
462     /** Toggles whether to load stream items. Just for debugging */
toggleLoadStreamItems()463     public void toggleLoadStreamItems() {
464         Loader<ContactLoader.Result> loaderObj = getLoaderManager().getLoader(LOADER_DETAILS);
465         ContactLoader loader = (ContactLoader) loaderObj;
466         loader.setLoadStreamItems(!loader.getLoadStreamItems());
467     }
468 
469     /** Returns whether to load stream items. Just for debugging */
getLoadStreamItems()470     public boolean getLoadStreamItems() {
471         Loader<ContactLoader.Result> loaderObj = getLoaderManager().getLoader(LOADER_DETAILS);
472         ContactLoader loader = (ContactLoader) loaderObj;
473         return loader != null && loader.getLoadStreamItems();
474     }
475 }
476