• 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.calllog;
18 
19 import android.app.Activity;
20 import android.app.KeyguardManager;
21 import android.app.ListFragment;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.database.ContentObserver;
25 import android.database.Cursor;
26 import android.net.Uri;
27 import android.os.Bundle;
28 import android.os.Handler;
29 import android.os.RemoteException;
30 import android.os.ServiceManager;
31 import android.provider.CallLog;
32 import android.provider.CallLog.Calls;
33 import android.provider.ContactsContract;
34 import android.telephony.PhoneNumberUtils;
35 import android.telephony.PhoneStateListener;
36 import android.telephony.TelephonyManager;
37 import android.text.TextUtils;
38 import android.util.Log;
39 import android.view.LayoutInflater;
40 import android.view.Menu;
41 import android.view.MenuInflater;
42 import android.view.MenuItem;
43 import android.view.View;
44 import android.view.ViewGroup;
45 import android.widget.ListView;
46 import android.widget.TextView;
47 
48 import com.android.common.io.MoreCloseables;
49 import com.android.contacts.ContactsUtils;
50 import com.android.contacts.R;
51 import com.android.contacts.util.Constants;
52 import com.android.contacts.util.EmptyLoader;
53 import com.android.contacts.voicemail.VoicemailStatusHelper;
54 import com.android.contacts.voicemail.VoicemailStatusHelper.StatusMessage;
55 import com.android.contacts.voicemail.VoicemailStatusHelperImpl;
56 import com.android.internal.telephony.CallerInfo;
57 import com.android.internal.telephony.ITelephony;
58 import com.google.common.annotations.VisibleForTesting;
59 
60 import java.util.List;
61 
62 /**
63  * Displays a list of call log entries.
64  */
65 public class CallLogFragment extends ListFragment
66         implements CallLogQueryHandler.Listener, CallLogAdapter.CallFetcher {
67     private static final String TAG = "CallLogFragment";
68 
69     /**
70      * ID of the empty loader to defer other fragments.
71      */
72     private static final int EMPTY_LOADER_ID = 0;
73 
74     private CallLogAdapter mAdapter;
75     private CallLogQueryHandler mCallLogQueryHandler;
76     private boolean mScrollToTop;
77 
78     /** Whether there is at least one voicemail source installed. */
79     private boolean mVoicemailSourcesAvailable = false;
80 
81     private VoicemailStatusHelper mVoicemailStatusHelper;
82     private View mStatusMessageView;
83     private TextView mStatusMessageText;
84     private TextView mStatusMessageAction;
85     private TextView mFilterStatusView;
86     private KeyguardManager mKeyguardManager;
87 
88     private boolean mEmptyLoaderRunning;
89     private boolean mCallLogFetched;
90     private boolean mVoicemailStatusFetched;
91 
92     private final Handler mHandler = new Handler();
93 
94     private TelephonyManager mTelephonyManager;
95     private PhoneStateListener mPhoneStateListener;
96 
97     private class CustomContentObserver extends ContentObserver {
CustomContentObserver()98         public CustomContentObserver() {
99             super(mHandler);
100         }
101         @Override
onChange(boolean selfChange)102         public void onChange(boolean selfChange) {
103             mRefreshDataRequired = true;
104         }
105     }
106 
107     // See issue 6363009
108     private final ContentObserver mCallLogObserver = new CustomContentObserver();
109     private final ContentObserver mContactsObserver = new CustomContentObserver();
110     private boolean mRefreshDataRequired = true;
111 
112     // Exactly same variable is in Fragment as a package private.
113     private boolean mMenuVisible = true;
114 
115     // Default to all calls.
116     private int mCallTypeFilter = CallLogQueryHandler.CALL_TYPE_ALL;
117 
118     @Override
onCreate(Bundle state)119     public void onCreate(Bundle state) {
120         super.onCreate(state);
121 
122         mCallLogQueryHandler = new CallLogQueryHandler(getActivity().getContentResolver(), this);
123         mKeyguardManager =
124                 (KeyguardManager) getActivity().getSystemService(Context.KEYGUARD_SERVICE);
125         getActivity().getContentResolver().registerContentObserver(
126                 CallLog.CONTENT_URI, true, mCallLogObserver);
127         getActivity().getContentResolver().registerContentObserver(
128                 ContactsContract.Contacts.CONTENT_URI, true, mContactsObserver);
129         setHasOptionsMenu(true);
130     }
131 
132     /** Called by the CallLogQueryHandler when the list of calls has been fetched or updated. */
133     @Override
onCallsFetched(Cursor cursor)134     public void onCallsFetched(Cursor cursor) {
135         if (getActivity() == null || getActivity().isFinishing()) {
136             return;
137         }
138         mAdapter.setLoading(false);
139         mAdapter.changeCursor(cursor);
140         // This will update the state of the "Clear call log" menu item.
141         getActivity().invalidateOptionsMenu();
142         if (mScrollToTop) {
143             final ListView listView = getListView();
144             // The smooth-scroll animation happens over a fixed time period.
145             // As a result, if it scrolls through a large portion of the list,
146             // each frame will jump so far from the previous one that the user
147             // will not experience the illusion of downward motion.  Instead,
148             // if we're not already near the top of the list, we instantly jump
149             // near the top, and animate from there.
150             if (listView.getFirstVisiblePosition() > 5) {
151                 listView.setSelection(5);
152             }
153             // Workaround for framework issue: the smooth-scroll doesn't
154             // occur if setSelection() is called immediately before.
155             mHandler.post(new Runnable() {
156                @Override
157                public void run() {
158                    if (getActivity() == null || getActivity().isFinishing()) {
159                        return;
160                    }
161                    listView.smoothScrollToPosition(0);
162                }
163             });
164 
165             mScrollToTop = false;
166         }
167         mCallLogFetched = true;
168         destroyEmptyLoaderIfAllDataFetched();
169     }
170 
171     /**
172      * Called by {@link CallLogQueryHandler} after a successful query to voicemail status provider.
173      */
174     @Override
onVoicemailStatusFetched(Cursor statusCursor)175     public void onVoicemailStatusFetched(Cursor statusCursor) {
176         if (getActivity() == null || getActivity().isFinishing()) {
177             return;
178         }
179         updateVoicemailStatusMessage(statusCursor);
180 
181         int activeSources = mVoicemailStatusHelper.getNumberActivityVoicemailSources(statusCursor);
182         setVoicemailSourcesAvailable(activeSources != 0);
183         MoreCloseables.closeQuietly(statusCursor);
184         mVoicemailStatusFetched = true;
185         destroyEmptyLoaderIfAllDataFetched();
186     }
187 
destroyEmptyLoaderIfAllDataFetched()188     private void destroyEmptyLoaderIfAllDataFetched() {
189         if (mCallLogFetched && mVoicemailStatusFetched && mEmptyLoaderRunning) {
190             mEmptyLoaderRunning = false;
191             getLoaderManager().destroyLoader(EMPTY_LOADER_ID);
192         }
193     }
194 
195     /** Sets whether there are any voicemail sources available in the platform. */
setVoicemailSourcesAvailable(boolean voicemailSourcesAvailable)196     private void setVoicemailSourcesAvailable(boolean voicemailSourcesAvailable) {
197         if (mVoicemailSourcesAvailable == voicemailSourcesAvailable) return;
198         mVoicemailSourcesAvailable = voicemailSourcesAvailable;
199 
200         Activity activity = getActivity();
201         if (activity != null) {
202             // This is so that the options menu content is updated.
203             activity.invalidateOptionsMenu();
204         }
205     }
206 
207     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState)208     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
209         View view = inflater.inflate(R.layout.call_log_fragment, container, false);
210         mVoicemailStatusHelper = new VoicemailStatusHelperImpl();
211         mStatusMessageView = view.findViewById(R.id.voicemail_status);
212         mStatusMessageText = (TextView) view.findViewById(R.id.voicemail_status_message);
213         mStatusMessageAction = (TextView) view.findViewById(R.id.voicemail_status_action);
214         mFilterStatusView = (TextView) view.findViewById(R.id.filter_status);
215         return view;
216     }
217 
218     @Override
onViewCreated(View view, Bundle savedInstanceState)219     public void onViewCreated(View view, Bundle savedInstanceState) {
220         super.onViewCreated(view, savedInstanceState);
221         String currentCountryIso = ContactsUtils.getCurrentCountryIso(getActivity());
222         mAdapter = new CallLogAdapter(getActivity(), this,
223                 new ContactInfoHelper(getActivity(), currentCountryIso));
224         setListAdapter(mAdapter);
225         getListView().setItemsCanFocus(true);
226     }
227 
228     /**
229      * Based on the new intent, decide whether the list should be configured
230      * to scroll up to display the first item.
231      */
configureScreenFromIntent(Intent newIntent)232     public void configureScreenFromIntent(Intent newIntent) {
233         // Typically, when switching to the call-log we want to show the user
234         // the same section of the list that they were most recently looking
235         // at.  However, under some circumstances, we want to automatically
236         // scroll to the top of the list to present the newest call items.
237         // For example, immediately after a call is finished, we want to
238         // display information about that call.
239         mScrollToTop = Calls.CONTENT_TYPE.equals(newIntent.getType());
240     }
241 
242     @Override
onStart()243     public void onStart() {
244         // Start the empty loader now to defer other fragments.  We destroy it when both calllog
245         // and the voicemail status are fetched.
246         getLoaderManager().initLoader(EMPTY_LOADER_ID, null,
247                 new EmptyLoader.Callback(getActivity()));
248         mEmptyLoaderRunning = true;
249         super.onStart();
250     }
251 
252     @Override
onResume()253     public void onResume() {
254         super.onResume();
255         refreshData();
256     }
257 
updateVoicemailStatusMessage(Cursor statusCursor)258     private void updateVoicemailStatusMessage(Cursor statusCursor) {
259         List<StatusMessage> messages = mVoicemailStatusHelper.getStatusMessages(statusCursor);
260         if (messages.size() == 0) {
261             mStatusMessageView.setVisibility(View.GONE);
262         } else {
263             mStatusMessageView.setVisibility(View.VISIBLE);
264             // TODO: Change the code to show all messages. For now just pick the first message.
265             final StatusMessage message = messages.get(0);
266             if (message.showInCallLog()) {
267                 mStatusMessageText.setText(message.callLogMessageId);
268             }
269             if (message.actionMessageId != -1) {
270                 mStatusMessageAction.setText(message.actionMessageId);
271             }
272             if (message.actionUri != null) {
273                 mStatusMessageAction.setVisibility(View.VISIBLE);
274                 mStatusMessageAction.setOnClickListener(new View.OnClickListener() {
275                     @Override
276                     public void onClick(View v) {
277                         getActivity().startActivity(
278                                 new Intent(Intent.ACTION_VIEW, message.actionUri));
279                     }
280                 });
281             } else {
282                 mStatusMessageAction.setVisibility(View.GONE);
283             }
284         }
285     }
286 
287     @Override
onPause()288     public void onPause() {
289         super.onPause();
290         // Kill the requests thread
291         mAdapter.stopRequestProcessing();
292     }
293 
294     @Override
onStop()295     public void onStop() {
296         super.onStop();
297         updateOnExit();
298     }
299 
300     @Override
onDestroy()301     public void onDestroy() {
302         super.onDestroy();
303         mAdapter.stopRequestProcessing();
304         mAdapter.changeCursor(null);
305         getActivity().getContentResolver().unregisterContentObserver(mCallLogObserver);
306         getActivity().getContentResolver().unregisterContentObserver(mContactsObserver);
307         unregisterPhoneCallReceiver();
308     }
309 
310     @Override
fetchCalls()311     public void fetchCalls() {
312         mCallLogQueryHandler.fetchCalls(mCallTypeFilter);
313     }
314 
startCallsQuery()315     public void startCallsQuery() {
316         mAdapter.setLoading(true);
317         mCallLogQueryHandler.fetchCalls(mCallTypeFilter);
318     }
319 
startVoicemailStatusQuery()320     private void startVoicemailStatusQuery() {
321         mCallLogQueryHandler.fetchVoicemailStatus();
322     }
323 
324     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)325     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
326         super.onCreateOptionsMenu(menu, inflater);
327         inflater.inflate(R.menu.call_log_options, menu);
328     }
329 
330     @Override
onPrepareOptionsMenu(Menu menu)331     public void onPrepareOptionsMenu(Menu menu) {
332         final MenuItem itemDeleteAll = menu.findItem(R.id.delete_all);
333         // Check if all the menu items are inflated correctly. As a shortcut, we assume all
334         // menu items are ready if the first item is non-null.
335         if (itemDeleteAll != null) {
336             itemDeleteAll.setEnabled(mAdapter != null && !mAdapter.isEmpty());
337 
338             showAllFilterMenuOptions(menu);
339             hideCurrentFilterMenuOption(menu);
340 
341             // Only hide if not available.  Let the above calls handle showing.
342             if (!mVoicemailSourcesAvailable) {
343                 menu.findItem(R.id.show_voicemails_only).setVisible(false);
344             }
345         }
346     }
347 
hideCurrentFilterMenuOption(Menu menu)348     private void hideCurrentFilterMenuOption(Menu menu) {
349         MenuItem item = null;
350         switch (mCallTypeFilter) {
351             case CallLogQueryHandler.CALL_TYPE_ALL:
352                 item = menu.findItem(R.id.show_all_calls);
353                 break;
354             case Calls.INCOMING_TYPE:
355                 item = menu.findItem(R.id.show_incoming_only);
356                 break;
357             case Calls.OUTGOING_TYPE:
358                 item = menu.findItem(R.id.show_outgoing_only);
359                 break;
360             case Calls.MISSED_TYPE:
361                 item = menu.findItem(R.id.show_missed_only);
362                 break;
363             case Calls.VOICEMAIL_TYPE:
364                 menu.findItem(R.id.show_voicemails_only);
365                 break;
366         }
367         if (item != null) {
368             item.setVisible(false);
369         }
370     }
371 
showAllFilterMenuOptions(Menu menu)372     private void showAllFilterMenuOptions(Menu menu) {
373         menu.findItem(R.id.show_all_calls).setVisible(true);
374         menu.findItem(R.id.show_incoming_only).setVisible(true);
375         menu.findItem(R.id.show_outgoing_only).setVisible(true);
376         menu.findItem(R.id.show_missed_only).setVisible(true);
377         menu.findItem(R.id.show_voicemails_only).setVisible(true);
378     }
379 
380     @Override
onOptionsItemSelected(MenuItem item)381     public boolean onOptionsItemSelected(MenuItem item) {
382         switch (item.getItemId()) {
383             case R.id.delete_all:
384                 ClearCallLogDialog.show(getFragmentManager());
385                 return true;
386 
387             case R.id.show_outgoing_only:
388                 // We only need the phone call receiver when there is an active call type filter.
389                 // Not many people may use the filters so don't register the receiver until now .
390                 registerPhoneCallReceiver();
391                 mCallLogQueryHandler.fetchCalls(Calls.OUTGOING_TYPE);
392                 updateFilterTypeAndHeader(Calls.OUTGOING_TYPE);
393                 return true;
394 
395             case R.id.show_incoming_only:
396                 registerPhoneCallReceiver();
397                 mCallLogQueryHandler.fetchCalls(Calls.INCOMING_TYPE);
398                 updateFilterTypeAndHeader(Calls.INCOMING_TYPE);
399                 return true;
400 
401             case R.id.show_missed_only:
402                 registerPhoneCallReceiver();
403                 mCallLogQueryHandler.fetchCalls(Calls.MISSED_TYPE);
404                 updateFilterTypeAndHeader(Calls.MISSED_TYPE);
405                 return true;
406 
407             case R.id.show_voicemails_only:
408                 registerPhoneCallReceiver();
409                 mCallLogQueryHandler.fetchCalls(Calls.VOICEMAIL_TYPE);
410                 updateFilterTypeAndHeader(Calls.VOICEMAIL_TYPE);
411                 return true;
412 
413             case R.id.show_all_calls:
414                 // Filter is being turned off, receiver no longer needed.
415                 unregisterPhoneCallReceiver();
416                 mCallLogQueryHandler.fetchCalls(CallLogQueryHandler.CALL_TYPE_ALL);
417                 updateFilterTypeAndHeader(CallLogQueryHandler.CALL_TYPE_ALL);
418                 return true;
419 
420             default:
421                 return false;
422         }
423     }
424 
updateFilterTypeAndHeader(int filterType)425     private void updateFilterTypeAndHeader(int filterType) {
426         mCallTypeFilter = filterType;
427 
428         switch (filterType) {
429             case CallLogQueryHandler.CALL_TYPE_ALL:
430                 mFilterStatusView.setVisibility(View.GONE);
431                 break;
432             case Calls.INCOMING_TYPE:
433                 showFilterStatus(R.string.call_log_incoming_header);
434                 break;
435             case Calls.OUTGOING_TYPE:
436                 showFilterStatus(R.string.call_log_outgoing_header);
437                 break;
438             case Calls.MISSED_TYPE:
439                 showFilterStatus(R.string.call_log_missed_header);
440                 break;
441             case Calls.VOICEMAIL_TYPE:
442                 showFilterStatus(R.string.call_log_voicemail_header);
443                 break;
444         }
445     }
446 
showFilterStatus(int resId)447     private void showFilterStatus(int resId) {
448         mFilterStatusView.setText(resId);
449         mFilterStatusView.setVisibility(View.VISIBLE);
450     }
451 
callSelectedEntry()452     public void callSelectedEntry() {
453         int position = getListView().getSelectedItemPosition();
454         if (position < 0) {
455             // In touch mode you may often not have something selected, so
456             // just call the first entry to make sure that [send] [send] calls the
457             // most recent entry.
458             position = 0;
459         }
460         final Cursor cursor = (Cursor)mAdapter.getItem(position);
461         if (cursor != null) {
462             String number = cursor.getString(CallLogQuery.NUMBER);
463             if (TextUtils.isEmpty(number)
464                     || number.equals(CallerInfo.UNKNOWN_NUMBER)
465                     || number.equals(CallerInfo.PRIVATE_NUMBER)
466                     || number.equals(CallerInfo.PAYPHONE_NUMBER)) {
467                 // This number can't be called, do nothing
468                 return;
469             }
470             Intent intent;
471             // If "number" is really a SIP address, construct a sip: URI.
472             if (PhoneNumberUtils.isUriNumber(number)) {
473                 intent = ContactsUtils.getCallIntent(
474                         Uri.fromParts(Constants.SCHEME_SIP, number, null));
475             } else {
476                 // We're calling a regular PSTN phone number.
477                 // Construct a tel: URI, but do some other possible cleanup first.
478                 int callType = cursor.getInt(CallLogQuery.CALL_TYPE);
479                 if (!number.startsWith("+") &&
480                        (callType == Calls.INCOMING_TYPE
481                                 || callType == Calls.MISSED_TYPE)) {
482                     // If the caller-id matches a contact with a better qualified number, use it
483                     String countryIso = cursor.getString(CallLogQuery.COUNTRY_ISO);
484                     number = mAdapter.getBetterNumberFromContacts(number, countryIso);
485                 }
486                 intent = ContactsUtils.getCallIntent(
487                         Uri.fromParts(Constants.SCHEME_TEL, number, null));
488             }
489             intent.setFlags(
490                     Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
491             startActivity(intent);
492         }
493     }
494 
495     @VisibleForTesting
getAdapter()496     CallLogAdapter getAdapter() {
497         return mAdapter;
498     }
499 
500     @Override
setMenuVisibility(boolean menuVisible)501     public void setMenuVisibility(boolean menuVisible) {
502         super.setMenuVisibility(menuVisible);
503         if (mMenuVisible != menuVisible) {
504             mMenuVisible = menuVisible;
505             if (!menuVisible) {
506                 updateOnExit();
507             } else if (isResumed()) {
508                 refreshData();
509             }
510         }
511     }
512 
513     /** Requests updates to the data to be shown. */
refreshData()514     private void refreshData() {
515         // Prevent unnecessary refresh.
516         if (mRefreshDataRequired) {
517             // Mark all entries in the contact info cache as out of date, so they will be looked up
518             // again once being shown.
519             mAdapter.invalidateCache();
520             startCallsQuery();
521             startVoicemailStatusQuery();
522             updateOnEntry();
523             mRefreshDataRequired = false;
524         }
525     }
526 
527     /** Removes the missed call notifications. */
removeMissedCallNotifications()528     private void removeMissedCallNotifications() {
529         try {
530             ITelephony telephony =
531                     ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
532             if (telephony != null) {
533                 telephony.cancelMissedCallsNotification();
534             } else {
535                 Log.w(TAG, "Telephony service is null, can't call " +
536                         "cancelMissedCallsNotification");
537             }
538         } catch (RemoteException e) {
539             Log.e(TAG, "Failed to clear missed calls notification due to remote exception");
540         }
541     }
542 
543     /** Updates call data and notification state while leaving the call log tab. */
updateOnExit()544     private void updateOnExit() {
545         updateOnTransition(false);
546     }
547 
548     /** Updates call data and notification state while entering the call log tab. */
updateOnEntry()549     private void updateOnEntry() {
550         updateOnTransition(true);
551     }
552 
updateOnTransition(boolean onEntry)553     private void updateOnTransition(boolean onEntry) {
554         // We don't want to update any call data when keyguard is on because the user has likely not
555         // seen the new calls yet.
556         // This might be called before onCreate() and thus we need to check null explicitly.
557         if (mKeyguardManager != null && !mKeyguardManager.inKeyguardRestrictedInputMode()) {
558             // On either of the transitions we reset the new flag and update the notifications.
559             // While exiting we additionally consume all missed calls (by marking them as read).
560             // This will ensure that they no more appear in the "new" section when we return back.
561             mCallLogQueryHandler.markNewCallsAsOld();
562             if (!onEntry) {
563                 mCallLogQueryHandler.markMissedCallsAsRead();
564             }
565             removeMissedCallNotifications();
566             updateVoicemailNotifications();
567         }
568     }
569 
updateVoicemailNotifications()570     private void updateVoicemailNotifications() {
571         Intent serviceIntent = new Intent(getActivity(), CallLogNotificationsService.class);
572         serviceIntent.setAction(CallLogNotificationsService.ACTION_UPDATE_NOTIFICATIONS);
573         getActivity().startService(serviceIntent);
574     }
575 
576     /**
577      * Register a phone call filter to reset the call type when a phone call is place.
578      */
registerPhoneCallReceiver()579     private void registerPhoneCallReceiver() {
580         if (mPhoneStateListener != null) {
581             return; // Already registered.
582         }
583         mTelephonyManager = (TelephonyManager) getActivity().getSystemService(
584                 Context.TELEPHONY_SERVICE);
585         mPhoneStateListener = new PhoneStateListener() {
586             @Override
587             public void onCallStateChanged(int state, String incomingNumber) {
588                 if (state != TelephonyManager.CALL_STATE_OFFHOOK &&
589                         state != TelephonyManager.CALL_STATE_RINGING) {
590                     return;
591                 }
592                 mHandler.post(new Runnable() {
593                     @Override
594                     public void run() {
595                         if (getActivity() == null || getActivity().isFinishing()) {
596                             return;
597                         }
598                         updateFilterTypeAndHeader(CallLogQueryHandler.CALL_TYPE_ALL);
599                     }
600                  });
601             }
602         };
603         mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
604     }
605 
606     /**
607      * Un-registers the phone call receiver.
608      */
unregisterPhoneCallReceiver()609     private void unregisterPhoneCallReceiver() {
610         if (mPhoneStateListener != null) {
611             mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
612             mPhoneStateListener = null;
613         }
614     }
615 }
616