Home | History | Annotate | Download | only in calllog
      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.dialer.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.common.CallUtil;
     50 import com.android.contacts.common.GeoUtil;
     51 import com.android.dialer.R;
     52 import com.android.dialer.util.EmptyLoader;
     53 import com.android.dialer.voicemail.VoicemailStatusHelper;
     54 import com.android.dialer.voicemail.VoicemailStatusHelper.StatusMessage;
     55 import com.android.dialer.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 {
     98         public CustomContentObserver() {
     99             super(mHandler);
    100         }
    101         @Override
    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
    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
    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
    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 
    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. */
    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
    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
    219     public void onViewCreated(View view, Bundle savedInstanceState) {
    220         super.onViewCreated(view, savedInstanceState);
    221         String currentCountryIso = GeoUtil.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      */
    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
    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
    253     public void onResume() {
    254         super.onResume();
    255         refreshData();
    256     }
    257 
    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
    288     public void onPause() {
    289         super.onPause();
    290         // Kill the requests thread
    291         mAdapter.stopRequestProcessing();
    292     }
    293 
    294     @Override
    295     public void onStop() {
    296         super.onStop();
    297         updateOnExit();
    298     }
    299 
    300     @Override
    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
    311     public void fetchCalls() {
    312         mCallLogQueryHandler.fetchCalls(mCallTypeFilter);
    313     }
    314 
    315     public void startCallsQuery() {
    316         mAdapter.setLoading(true);
    317         mCallLogQueryHandler.fetchCalls(mCallTypeFilter);
    318     }
    319 
    320     private void startVoicemailStatusQuery() {
    321         mCallLogQueryHandler.fetchVoicemailStatus();
    322     }
    323 
    324     @Override
    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
    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 
    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 
    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
    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 
    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 
    447     private void showFilterStatus(int resId) {
    448         mFilterStatusView.setText(resId);
    449         mFilterStatusView.setVisibility(View.VISIBLE);
    450     }
    451 
    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 = CallUtil.getCallIntent(
    474                         Uri.fromParts(CallUtil.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 = CallUtil.getCallIntent(
    487                         Uri.fromParts(CallUtil.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
    496     CallLogAdapter getAdapter() {
    497         return mAdapter;
    498     }
    499 
    500     @Override
    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. */
    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. */
    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. */
    544     private void updateOnExit() {
    545         updateOnTransition(false);
    546     }
    547 
    548     /** Updates call data and notification state while entering the call log tab. */
    549     private void updateOnEntry() {
    550         updateOnTransition(true);
    551     }
    552 
    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 
    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      */
    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      */
    609     private void unregisterPhoneCallReceiver() {
    610         if (mPhoneStateListener != null) {
    611             mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
    612             mPhoneStateListener = null;
    613         }
    614     }
    615 }
    616