Home | History | Annotate | Download | only in phone
      1 /*
      2  * Copyright (C) 2006 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.phone;
     18 
     19 import android.app.Notification;
     20 import android.app.NotificationManager;
     21 import android.app.PendingIntent;
     22 import android.app.StatusBarManager;
     23 import android.content.AsyncQueryHandler;
     24 import android.content.ComponentName;
     25 import android.content.ContentResolver;
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.content.SharedPreferences;
     29 import android.database.Cursor;
     30 import android.media.AudioManager;
     31 import android.net.Uri;
     32 import android.os.IBinder;
     33 import android.os.SystemClock;
     34 import android.os.SystemProperties;
     35 import android.preference.PreferenceManager;
     36 import android.provider.Settings;
     37 import android.provider.CallLog.Calls;
     38 import android.provider.ContactsContract.PhoneLookup;
     39 import android.telephony.PhoneNumberUtils;
     40 import android.telephony.ServiceState;
     41 import android.text.TextUtils;
     42 import android.util.Log;
     43 import android.widget.RemoteViews;
     44 import android.widget.Toast;
     45 
     46 import com.android.internal.telephony.Call;
     47 import com.android.internal.telephony.CallerInfo;
     48 import com.android.internal.telephony.CallerInfoAsyncQuery;
     49 import com.android.internal.telephony.Connection;
     50 import com.android.internal.telephony.Phone;
     51 import com.android.internal.telephony.PhoneBase;
     52 
     53 
     54 /**
     55  * NotificationManager-related utility code for the Phone app.
     56  */
     57 public class NotificationMgr implements CallerInfoAsyncQuery.OnQueryCompleteListener{
     58     private static final String LOG_TAG = "NotificationMgr";
     59     private static final boolean DBG = (PhoneApp.DBG_LEVEL >= 2);
     60 
     61     private static final String[] CALL_LOG_PROJECTION = new String[] {
     62         Calls._ID,
     63         Calls.NUMBER,
     64         Calls.DATE,
     65         Calls.DURATION,
     66         Calls.TYPE,
     67     };
     68 
     69     // notification types
     70     static final int MISSED_CALL_NOTIFICATION = 1;
     71     static final int IN_CALL_NOTIFICATION = 2;
     72     static final int MMI_NOTIFICATION = 3;
     73     static final int NETWORK_SELECTION_NOTIFICATION = 4;
     74     static final int VOICEMAIL_NOTIFICATION = 5;
     75     static final int CALL_FORWARD_NOTIFICATION = 6;
     76     static final int DATA_DISCONNECTED_ROAMING_NOTIFICATION = 7;
     77     static final int SELECTED_OPERATOR_FAIL_NOTIFICATION = 8;
     78 
     79     private static NotificationMgr sMe = null;
     80     private Phone mPhone;
     81 
     82     private Context mContext;
     83     private NotificationManager mNotificationMgr;
     84     private StatusBarManager mStatusBar;
     85     private StatusBarMgr mStatusBarMgr;
     86     private Toast mToast;
     87     private IBinder mSpeakerphoneIcon;
     88     private IBinder mMuteIcon;
     89 
     90     // used to track the missed call counter, default to 0.
     91     private int mNumberMissedCalls = 0;
     92 
     93     // Currently-displayed resource IDs for some status bar icons (or zero
     94     // if no notification is active):
     95     private int mInCallResId;
     96 
     97     // used to track the notification of selected network unavailable
     98     private boolean mSelectedUnavailableNotify = false;
     99 
    100     // Retry params for the getVoiceMailNumber() call; see updateMwi().
    101     private static final int MAX_VM_NUMBER_RETRIES = 5;
    102     private static final int VM_NUMBER_RETRY_DELAY_MILLIS = 10000;
    103     private int mVmNumberRetriesRemaining = MAX_VM_NUMBER_RETRIES;
    104 
    105     // Query used to look up caller-id info for the "call log" notification.
    106     private QueryHandler mQueryHandler = null;
    107     private static final int CALL_LOG_TOKEN = -1;
    108     private static final int CONTACT_TOKEN = -2;
    109 
    110     NotificationMgr(Context context) {
    111         mContext = context;
    112         mNotificationMgr = (NotificationManager)
    113             context.getSystemService(Context.NOTIFICATION_SERVICE);
    114 
    115         mStatusBar = (StatusBarManager) context.getSystemService(Context.STATUS_BAR_SERVICE);
    116 
    117         PhoneApp app = PhoneApp.getInstance();
    118         mPhone = app.phone;
    119     }
    120 
    121     static void init(Context context) {
    122         sMe = new NotificationMgr(context);
    123 
    124         // update the notifications that need to be touched at startup.
    125         sMe.updateNotificationsAtStartup();
    126     }
    127 
    128     static NotificationMgr getDefault() {
    129         return sMe;
    130     }
    131 
    132     /**
    133      * Class that controls the status bar.  This class maintains a set
    134      * of state and acts as an interface between the Phone process and
    135      * the Status bar.  All interaction with the status bar should be
    136      * though the methods contained herein.
    137      */
    138 
    139     /**
    140      * Factory method
    141      */
    142     StatusBarMgr getStatusBarMgr() {
    143         if (mStatusBarMgr == null) {
    144             mStatusBarMgr = new StatusBarMgr();
    145         }
    146         return mStatusBarMgr;
    147     }
    148 
    149     /**
    150      * StatusBarMgr implementation
    151      */
    152     class StatusBarMgr {
    153         // current settings
    154         private boolean mIsNotificationEnabled = true;
    155         private boolean mIsExpandedViewEnabled = true;
    156 
    157         private StatusBarMgr () {
    158         }
    159 
    160         /**
    161          * Sets the notification state (enable / disable
    162          * vibrating notifications) for the status bar,
    163          * updates the status bar service if there is a change.
    164          * Independent of the remaining Status Bar
    165          * functionality, including icons and expanded view.
    166          */
    167         void enableNotificationAlerts(boolean enable) {
    168             if (mIsNotificationEnabled != enable) {
    169                 mIsNotificationEnabled = enable;
    170                 updateStatusBar();
    171             }
    172         }
    173 
    174         /**
    175          * Sets the ability to expand the notifications for the
    176          * status bar, updates the status bar service if there
    177          * is a change. Independent of the remaining Status Bar
    178          * functionality, including icons and notification
    179          * alerts.
    180          */
    181         void enableExpandedView(boolean enable) {
    182             if (mIsExpandedViewEnabled != enable) {
    183                 mIsExpandedViewEnabled = enable;
    184                 updateStatusBar();
    185             }
    186         }
    187 
    188         /**
    189          * Method to synchronize status bar state with our current
    190          * state.
    191          */
    192         void updateStatusBar() {
    193             int state = StatusBarManager.DISABLE_NONE;
    194 
    195             if (!mIsExpandedViewEnabled) {
    196                 state |= StatusBarManager.DISABLE_EXPAND;
    197             }
    198 
    199             if (!mIsNotificationEnabled) {
    200                 state |= StatusBarManager.DISABLE_NOTIFICATION_ALERTS;
    201             }
    202 
    203             // send the message to the status bar manager.
    204             if (DBG) log("updating status bar state: " + state);
    205             mStatusBar.disable(state);
    206         }
    207     }
    208 
    209     /**
    210      * Makes sure phone-related notifications are up to date on a
    211      * freshly-booted device.
    212      */
    213     private void updateNotificationsAtStartup() {
    214         if (DBG) log("updateNotificationsAtStartup()...");
    215 
    216         // instantiate query handler
    217         mQueryHandler = new QueryHandler(mContext.getContentResolver());
    218 
    219         // setup query spec, look for all Missed calls that are new.
    220         StringBuilder where = new StringBuilder("type=");
    221         where.append(Calls.MISSED_TYPE);
    222         where.append(" AND new=1");
    223 
    224         // start the query
    225         mQueryHandler.startQuery(CALL_LOG_TOKEN, null, Calls.CONTENT_URI,  CALL_LOG_PROJECTION,
    226                 where.toString(), null, Calls.DEFAULT_SORT_ORDER);
    227 
    228         // synchronize the in call notification
    229         if (mPhone.getState() != Phone.State.OFFHOOK) {
    230             if (DBG) log("Phone is idle, canceling notification.");
    231             cancelInCall();
    232         } else {
    233             if (DBG) log("Phone is offhook, updating notification.");
    234             updateInCallNotification();
    235         }
    236 
    237         // Depend on android.app.StatusBarManager to be set to
    238         // disable(DISABLE_NONE) upon startup.  This will be the
    239         // case even if the phone app crashes.
    240     }
    241 
    242     /** The projection to use when querying the phones table */
    243     static final String[] PHONES_PROJECTION = new String[] {
    244         PhoneLookup.NUMBER,
    245         PhoneLookup.DISPLAY_NAME
    246     };
    247 
    248     /**
    249      * Class used to run asynchronous queries to re-populate
    250      * the notifications we care about.
    251      */
    252     private class QueryHandler extends AsyncQueryHandler {
    253 
    254         /**
    255          * Used to store relevant fields for the Missed Call
    256          * notifications.
    257          */
    258         private class NotificationInfo {
    259             public String name;
    260             public String number;
    261             public String label;
    262             public long date;
    263         }
    264 
    265         public QueryHandler(ContentResolver cr) {
    266             super(cr);
    267         }
    268 
    269         /**
    270          * Handles the query results.  There are really 2 steps to this,
    271          * similar to what happens in RecentCallsListActivity.
    272          *  1. Find the list of missed calls
    273          *  2. For each call, run a query to retrieve the caller's name.
    274          */
    275         @Override
    276         protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
    277             // TODO: it would be faster to use a join here, but for the purposes
    278             // of this small record set, it should be ok.
    279 
    280             // Note that CursorJoiner is not useable here because the number
    281             // comparisons are not strictly equals; the comparisons happen in
    282             // the SQL function PHONE_NUMBERS_EQUAL, which is not available for
    283             // the CursorJoiner.
    284 
    285             // Executing our own query is also feasible (with a join), but that
    286             // will require some work (possibly destabilizing) in Contacts
    287             // Provider.
    288 
    289             // At this point, we will execute subqueries on each row just as
    290             // RecentCallsListActivity.java does.
    291             switch (token) {
    292                 case CALL_LOG_TOKEN:
    293                     if (DBG) log("call log query complete.");
    294 
    295                     // initial call to retrieve the call list.
    296                     if (cursor != null) {
    297                         while (cursor.moveToNext()) {
    298                             // for each call in the call log list, create
    299                             // the notification object and query contacts
    300                             NotificationInfo n = getNotificationInfo (cursor);
    301 
    302                             if (DBG) log("query contacts for number: " + n.number);
    303 
    304                             mQueryHandler.startQuery(CONTACT_TOKEN, n,
    305                                     Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, n.number),
    306                                     PHONES_PROJECTION, null, null, PhoneLookup.NUMBER);
    307                         }
    308 
    309                         if (DBG) log("closing call log cursor.");
    310                         cursor.close();
    311                     }
    312                     break;
    313                 case CONTACT_TOKEN:
    314                     if (DBG) log("contact query complete.");
    315 
    316                     // subqueries to get the caller name.
    317                     if ((cursor != null) && (cookie != null)){
    318                         NotificationInfo n = (NotificationInfo) cookie;
    319 
    320                         if (cursor.moveToFirst()) {
    321                             // we have contacts data, get the name.
    322                             if (DBG) log("contact :" + n.name + " found for phone: " + n.number);
    323                             n.name = cursor.getString(
    324                                     cursor.getColumnIndexOrThrow(PhoneLookup.DISPLAY_NAME));
    325                         }
    326 
    327                         // send the notification
    328                         if (DBG) log("sending notification.");
    329                         notifyMissedCall(n.name, n.number, n.label, n.date);
    330 
    331                         if (DBG) log("closing contact cursor.");
    332                         cursor.close();
    333                     }
    334                     break;
    335                 default:
    336             }
    337         }
    338 
    339         /**
    340          * Factory method to generate a NotificationInfo object given a
    341          * cursor from the call log table.
    342          */
    343         private final NotificationInfo getNotificationInfo(Cursor cursor) {
    344             NotificationInfo n = new NotificationInfo();
    345             n.name = null;
    346             n.number = cursor.getString(cursor.getColumnIndexOrThrow(Calls.NUMBER));
    347             n.label = cursor.getString(cursor.getColumnIndexOrThrow(Calls.TYPE));
    348             n.date = cursor.getLong(cursor.getColumnIndexOrThrow(Calls.DATE));
    349 
    350             // make sure we update the number depending upon saved values in
    351             // CallLog.addCall().  If either special values for unknown or
    352             // private number are detected, we need to hand off the message
    353             // to the missed call notification.
    354             if ( (n.number.equals(CallerInfo.UNKNOWN_NUMBER)) ||
    355                  (n.number.equals(CallerInfo.PRIVATE_NUMBER)) ||
    356                  (n.number.equals(CallerInfo.PAYPHONE_NUMBER)) ) {
    357                 n.number = null;
    358             }
    359 
    360             if (DBG) log("NotificationInfo constructed for number: " + n.number);
    361 
    362             return n;
    363         }
    364     }
    365 
    366     /**
    367      * Configures a Notification to emit the blinky green message-waiting/
    368      * missed-call signal.
    369      */
    370     private static void configureLedNotification(Notification note) {
    371         note.flags |= Notification.FLAG_SHOW_LIGHTS;
    372         note.defaults |= Notification.DEFAULT_LIGHTS;
    373     }
    374 
    375     /**
    376      * Displays a notification about a missed call.
    377      *
    378      * @param nameOrNumber either the contact name, or the phone number if no contact
    379      * @param label the label of the number if nameOrNumber is a name, null if it is a number
    380      */
    381     void notifyMissedCall(String name, String number, String label, long date) {
    382         // title resource id
    383         int titleResId;
    384         // the text in the notification's line 1 and 2.
    385         String expandedText, callName;
    386 
    387         // increment number of missed calls.
    388         mNumberMissedCalls++;
    389 
    390         // get the name for the ticker text
    391         // i.e. "Missed call from <caller name or number>"
    392         if (name != null && TextUtils.isGraphic(name)) {
    393             callName = name;
    394         } else if (!TextUtils.isEmpty(number)){
    395             callName = number;
    396         } else {
    397             // use "unknown" if the caller is unidentifiable.
    398             callName = mContext.getString(R.string.unknown);
    399         }
    400 
    401         // display the first line of the notification:
    402         // 1 missed call: call name
    403         // more than 1 missed call: <number of calls> + "missed calls"
    404         if (mNumberMissedCalls == 1) {
    405             titleResId = R.string.notification_missedCallTitle;
    406             expandedText = callName;
    407         } else {
    408             titleResId = R.string.notification_missedCallsTitle;
    409             expandedText = mContext.getString(R.string.notification_missedCallsMsg,
    410                     mNumberMissedCalls);
    411         }
    412 
    413         // create the target call log intent
    414         final Intent intent = PhoneApp.createCallLogIntent();
    415 
    416         // make the notification
    417         Notification note = new Notification(mContext, // context
    418                 android.R.drawable.stat_notify_missed_call, // icon
    419                 mContext.getString(R.string.notification_missedCallTicker, callName), // tickerText
    420                 date, // when
    421                 mContext.getText(titleResId), // expandedTitle
    422                 expandedText, // expandedText
    423                 intent // contentIntent
    424                 );
    425         configureLedNotification(note);
    426         mNotificationMgr.notify(MISSED_CALL_NOTIFICATION, note);
    427     }
    428 
    429     void cancelMissedCallNotification() {
    430         // reset the number of missed calls to 0.
    431         mNumberMissedCalls = 0;
    432         mNotificationMgr.cancel(MISSED_CALL_NOTIFICATION);
    433     }
    434 
    435     void notifySpeakerphone() {
    436         if (mSpeakerphoneIcon == null) {
    437             mSpeakerphoneIcon = mStatusBar.addIcon("speakerphone",
    438                     android.R.drawable.stat_sys_speakerphone, 0);
    439         }
    440     }
    441 
    442     void cancelSpeakerphone() {
    443         if (mSpeakerphoneIcon != null) {
    444             mStatusBar.removeIcon(mSpeakerphoneIcon);
    445             mSpeakerphoneIcon = null;
    446         }
    447     }
    448 
    449     /**
    450      * Calls either notifySpeakerphone() or cancelSpeakerphone() based on
    451      * the actual current state of the speaker.
    452      */
    453     void updateSpeakerNotification() {
    454         AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
    455 
    456         if ((mPhone.getState() == Phone.State.OFFHOOK) && audioManager.isSpeakerphoneOn()) {
    457             if (DBG) log("updateSpeakerNotification: speaker ON");
    458             notifySpeakerphone();
    459         } else {
    460             if (DBG) log("updateSpeakerNotification: speaker OFF (or not offhook)");
    461             cancelSpeakerphone();
    462         }
    463     }
    464 
    465     void notifyMute() {
    466         if (mMuteIcon == null) {
    467             mMuteIcon = mStatusBar.addIcon("mute", android.R.drawable.stat_notify_call_mute, 0);
    468         }
    469     }
    470 
    471     void cancelMute() {
    472         if (mMuteIcon != null) {
    473             mStatusBar.removeIcon(mMuteIcon);
    474             mMuteIcon = null;
    475         }
    476     }
    477 
    478     /**
    479      * Calls either notifyMute() or cancelMute() based on
    480      * the actual current mute state of the Phone.
    481      */
    482     void updateMuteNotification() {
    483         if ((mPhone.getState() == Phone.State.OFFHOOK) && mPhone.getMute()) {
    484             if (DBG) log("updateMuteNotification: MUTED");
    485             notifyMute();
    486         } else {
    487             if (DBG) log("updateMuteNotification: not muted (or not offhook)");
    488             cancelMute();
    489         }
    490     }
    491 
    492     void updateInCallNotification() {
    493         int resId;
    494         if (DBG) log("updateInCallNotification()...");
    495 
    496         if (mPhone.getState() != Phone.State.OFFHOOK) {
    497             return;
    498         }
    499 
    500         final boolean hasActiveCall = !mPhone.getForegroundCall().isIdle();
    501         final boolean hasHoldingCall = !mPhone.getBackgroundCall().isIdle();
    502 
    503         // Display the appropriate "in-call" icon in the status bar,
    504         // which depends on the current phone and/or bluetooth state.
    505 
    506 
    507         boolean enhancedVoicePrivacy = PhoneApp.getInstance().notifier.getCdmaVoicePrivacyState();
    508         if (DBG) log("updateInCallNotification: enhancedVoicePrivacy = " + enhancedVoicePrivacy);
    509 
    510         if (!hasActiveCall && hasHoldingCall) {
    511             // There's only one call, and it's on hold.
    512             if (enhancedVoicePrivacy) {
    513                 resId = android.R.drawable.stat_sys_vp_phone_call_on_hold;
    514             } else {
    515                 resId = android.R.drawable.stat_sys_phone_call_on_hold;
    516             }
    517         } else if (PhoneApp.getInstance().showBluetoothIndication()) {
    518             // Bluetooth is active.
    519             if (enhancedVoicePrivacy) {
    520                 resId = com.android.internal.R.drawable.stat_sys_vp_phone_call_bluetooth;
    521             } else {
    522                 resId = com.android.internal.R.drawable.stat_sys_phone_call_bluetooth;
    523             }
    524         } else {
    525             if (enhancedVoicePrivacy) {
    526                 resId = android.R.drawable.stat_sys_vp_phone_call;
    527             } else {
    528                 resId = android.R.drawable.stat_sys_phone_call;
    529             }
    530         }
    531 
    532         // Note we can't just bail out now if (resId == mInCallResId),
    533         // since even if the status icon hasn't changed, some *other*
    534         // notification-related info may be different from the last time
    535         // we were here (like the caller-id info of the foreground call,
    536         // if the user swapped calls...)
    537 
    538         if (DBG) log("- Updating status bar icon: " + resId);
    539         mInCallResId = resId;
    540 
    541         // Even if both lines are in use, we only show a single item in
    542         // the expanded Notifications UI.  It's labeled "Ongoing call"
    543         // (or "On hold" if there's only one call, and it's on hold.)
    544 
    545         // The icon in the expanded view is the same as in the status bar.
    546         int expandedViewIcon = mInCallResId;
    547 
    548         // Also, we don't have room to display caller-id info from two
    549         // different calls.  So if there's only one call, use that, but if
    550         // both lines are in use we display the caller-id info from the
    551         // foreground call and totally ignore the background call.
    552         Call currentCall = hasActiveCall ? mPhone.getForegroundCall()
    553                 : mPhone.getBackgroundCall();
    554         Connection currentConn = currentCall.getEarliestConnection();
    555 
    556         // When expanded, the "Ongoing call" notification is (visually)
    557         // different from most other Notifications, so we need to use a
    558         // custom view hierarchy.
    559 
    560         Notification notification = new Notification();
    561         notification.icon = mInCallResId;
    562         notification.contentIntent = PendingIntent.getActivity(mContext, 0,
    563                 PhoneApp.createInCallIntent(), 0);
    564         notification.flags |= Notification.FLAG_ONGOING_EVENT;
    565 
    566         // Our custom view, which includes an icon (either "ongoing call" or
    567         // "on hold") and 2 lines of text: (1) the label (either "ongoing
    568         // call" with time counter, or "on hold), and (2) the compact name of
    569         // the current Connection.
    570         RemoteViews contentView = new RemoteViews(mContext.getPackageName(),
    571                                                    R.layout.ongoing_call_notification);
    572         contentView.setImageViewResource(R.id.icon, expandedViewIcon);
    573 
    574         // if the connection is valid, then build what we need for the
    575         // first line of notification information, and start the chronometer.
    576         // Otherwise, don't bother and just stick with line 2.
    577         if (currentConn != null) {
    578             // Determine the "start time" of the current connection, in terms
    579             // of the SystemClock.elapsedRealtime() timebase (which is what
    580             // the Chronometer widget needs.)
    581             //   We can't use currentConn.getConnectTime(), because (1) that's
    582             // in the currentTimeMillis() time base, and (2) it's zero when
    583             // the phone first goes off hook, since the getConnectTime counter
    584             // doesn't start until the DIALING -> ACTIVE transition.
    585             //   Instead we start with the current connection's duration,
    586             // and translate that into the elapsedRealtime() timebase.
    587             long callDurationMsec = currentConn.getDurationMillis();
    588             long chronometerBaseTime = SystemClock.elapsedRealtime() - callDurationMsec;
    589 
    590             // Line 1 of the expanded view (in bold text):
    591             String expandedViewLine1;
    592             if (hasHoldingCall && !hasActiveCall) {
    593                 // Only one call, and it's on hold!
    594                 // Note this isn't a format string!  (We want "On hold" here,
    595                 // not "On hold (1:23)".)  That's OK; if you call
    596                 // String.format() with more arguments than format specifiers,
    597                 // the extra arguments are ignored.
    598                 expandedViewLine1 = mContext.getString(R.string.notification_on_hold);
    599             } else {
    600                 // Format string with a "%s" where the current call time should go.
    601                 expandedViewLine1 = mContext.getString(R.string.notification_ongoing_call_format);
    602             }
    603 
    604             if (DBG) log("- Updating expanded view: line 1 '" + expandedViewLine1 + "'");
    605 
    606             // Text line #1 is actually a Chronometer, not a plain TextView.
    607             // We format the elapsed time of the current call into a line like
    608             // "Ongoing call (01:23)".
    609             contentView.setChronometer(R.id.text1,
    610                                        chronometerBaseTime,
    611                                        expandedViewLine1,
    612                                        true);
    613         } else if (DBG) {
    614             log("updateInCallNotification: connection is null, call status not updated.");
    615         }
    616 
    617         // display conference call string if this call is a conference
    618         // call, otherwise display the connection information.
    619 
    620         // TODO: it may not make sense for every point to make separate
    621         // checks for isConferenceCall, so we need to think about
    622         // possibly including this in startGetCallerInfo or some other
    623         // common point.
    624         String expandedViewLine2 = "";
    625         if (PhoneUtils.isConferenceCall(currentCall)) {
    626             // if this is a conference call, just use that as the caller name.
    627             expandedViewLine2 = mContext.getString(R.string.card_title_conf_call);
    628         } else {
    629             // Start asynchronous call to get the compact name.
    630             PhoneUtils.CallerInfoToken cit =
    631                 PhoneUtils.startGetCallerInfo (mContext, currentCall, this, contentView);
    632             // Line 2 of the expanded view (smaller text):
    633             expandedViewLine2 = PhoneUtils.getCompactNameFromCallerInfo(cit.currentInfo, mContext);
    634         }
    635 
    636         if (DBG) log("- Updating expanded view: line 2 '" + expandedViewLine2 + "'");
    637         contentView.setTextViewText(R.id.text2, expandedViewLine2);
    638         notification.contentView = contentView;
    639 
    640         // TODO: We also need to *update* this notification in some cases,
    641         // like when a call ends on one line but the other is still in use
    642         // (ie. make sure the caller info here corresponds to the active
    643         // line), and maybe even when the user swaps calls (ie. if we only
    644         // show info here for the "current active call".)
    645 
    646         if (DBG) log("Notifying IN_CALL_NOTIFICATION: " + notification);
    647         mNotificationMgr.notify(IN_CALL_NOTIFICATION,
    648                                 notification);
    649 
    650         // Finally, refresh the mute and speakerphone notifications (since
    651         // some phone state changes can indirectly affect the mute and/or
    652         // speaker state).
    653         updateSpeakerNotification();
    654         updateMuteNotification();
    655     }
    656 
    657     /**
    658      * Implemented for CallerInfoAsyncQuery.OnQueryCompleteListener interface.
    659      * refreshes the contentView when called.
    660      */
    661     public void onQueryComplete(int token, Object cookie, CallerInfo ci){
    662         if (DBG) log("callerinfo query complete, updating ui.");
    663 
    664         ((RemoteViews) cookie).setTextViewText(R.id.text2,
    665                 PhoneUtils.getCompactNameFromCallerInfo(ci, mContext));
    666     }
    667 
    668     private void cancelInCall() {
    669         if (DBG) log("cancelInCall()...");
    670         cancelMute();
    671         cancelSpeakerphone();
    672         mNotificationMgr.cancel(IN_CALL_NOTIFICATION);
    673         mInCallResId = 0;
    674     }
    675 
    676     void cancelCallInProgressNotification() {
    677         if (DBG) log("cancelCallInProgressNotification()...");
    678         if (mInCallResId == 0) {
    679             return;
    680         }
    681 
    682         if (DBG) log("cancelCallInProgressNotification: " + mInCallResId);
    683         cancelInCall();
    684     }
    685 
    686     /**
    687      * Updates the message waiting indicator (voicemail) notification.
    688      *
    689      * @param visible true if there are messages waiting
    690      */
    691     /* package */ void updateMwi(boolean visible) {
    692         if (DBG) log("updateMwi(): " + visible);
    693         if (visible) {
    694             int resId = android.R.drawable.stat_notify_voicemail;
    695 
    696             // This Notification can get a lot fancier once we have more
    697             // information about the current voicemail messages.
    698             // (For example, the current voicemail system can't tell
    699             // us the caller-id or timestamp of a message, or tell us the
    700             // message count.)
    701 
    702             // But for now, the UI is ultra-simple: if the MWI indication
    703             // is supposed to be visible, just show a single generic
    704             // notification.
    705 
    706             String notificationTitle = mContext.getString(R.string.notification_voicemail_title);
    707             String vmNumber = mPhone.getVoiceMailNumber();
    708             if (DBG) log("- got vm number: '" + vmNumber + "'");
    709 
    710             // Watch out: vmNumber may be null, for two possible reasons:
    711             //
    712             //   (1) This phone really has no voicemail number
    713             //
    714             //   (2) This phone *does* have a voicemail number, but
    715             //       the SIM isn't ready yet.
    716             //
    717             // Case (2) *does* happen in practice if you have voicemail
    718             // messages when the device first boots: we get an MWI
    719             // notification as soon as we register on the network, but the
    720             // SIM hasn't finished loading yet.
    721             //
    722             // So handle case (2) by retrying the lookup after a short
    723             // delay.
    724 
    725             if ((vmNumber == null) && !mPhone.getIccRecordsLoaded()) {
    726                 if (DBG) log("- Null vm number: SIM records not loaded (yet)...");
    727 
    728                 // TODO: rather than retrying after an arbitrary delay, it
    729                 // would be cleaner to instead just wait for a
    730                 // SIM_RECORDS_LOADED notification.
    731                 // (Unfortunately right now there's no convenient way to
    732                 // get that notification in phone app code.  We'd first
    733                 // want to add a call like registerForSimRecordsLoaded()
    734                 // to Phone.java and GSMPhone.java, and *then* we could
    735                 // listen for that in the CallNotifier class.)
    736 
    737                 // Limit the number of retries (in case the SIM is broken
    738                 // or missing and can *never* load successfully.)
    739                 if (mVmNumberRetriesRemaining-- > 0) {
    740                     if (DBG) log("  - Retrying in " + VM_NUMBER_RETRY_DELAY_MILLIS + " msec...");
    741                     PhoneApp.getInstance().notifier.sendMwiChangedDelayed(
    742                             VM_NUMBER_RETRY_DELAY_MILLIS);
    743                     return;
    744                 } else {
    745                     Log.w(LOG_TAG, "NotificationMgr.updateMwi: getVoiceMailNumber() failed after "
    746                           + MAX_VM_NUMBER_RETRIES + " retries; giving up.");
    747                     // ...and continue with vmNumber==null, just as if the
    748                     // phone had no VM number set up in the first place.
    749                 }
    750             }
    751 
    752             if (mPhone.getPhoneType() == Phone.PHONE_TYPE_CDMA) {
    753                 int vmCount = mPhone.getVoiceMessageCount();
    754                 String titleFormat = mContext.getString(R.string.notification_voicemail_title_count);
    755                 notificationTitle = String.format(titleFormat, vmCount);
    756             }
    757 
    758             String notificationText;
    759             if (TextUtils.isEmpty(vmNumber)) {
    760                 notificationText = mContext.getString(
    761                         R.string.notification_voicemail_no_vm_number);
    762             } else {
    763                 notificationText = String.format(
    764                         mContext.getString(R.string.notification_voicemail_text_format),
    765                         PhoneNumberUtils.formatNumber(vmNumber));
    766             }
    767 
    768             Intent intent = new Intent(Intent.ACTION_CALL,
    769                     Uri.fromParts("voicemail", "", null));
    770             PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
    771 
    772             Notification notification = new Notification(
    773                     resId,  // icon
    774                     null, // tickerText
    775                     System.currentTimeMillis()  // Show the time the MWI notification came in,
    776                                                 // since we don't know the actual time of the
    777                                                 // most recent voicemail message
    778                     );
    779             notification.setLatestEventInfo(
    780                     mContext,  // context
    781                     notificationTitle,  // contentTitle
    782                     notificationText,  // contentText
    783                     pendingIntent  // contentIntent
    784                     );
    785             notification.defaults |= Notification.DEFAULT_SOUND;
    786             notification.flags |= Notification.FLAG_NO_CLEAR;
    787             configureLedNotification(notification);
    788             mNotificationMgr.notify(VOICEMAIL_NOTIFICATION, notification);
    789         } else {
    790             mNotificationMgr.cancel(VOICEMAIL_NOTIFICATION);
    791         }
    792     }
    793 
    794     /**
    795      * Updates the message call forwarding indicator notification.
    796      *
    797      * @param visible true if there are messages waiting
    798      */
    799     /* package */ void updateCfi(boolean visible) {
    800         if (DBG) log("updateCfi(): " + visible);
    801         if (visible) {
    802             // If Unconditional Call Forwarding (forward all calls) for VOICE
    803             // is enabled, just show a notification.  We'll default to expanded
    804             // view for now, so the there is less confusion about the icon.  If
    805             // it is deemed too weird to have CF indications as expanded views,
    806             // then we'll flip the flag back.
    807 
    808             // TODO: We may want to take a look to see if the notification can
    809             // display the target to forward calls to.  This will require some
    810             // effort though, since there are multiple layers of messages that
    811             // will need to propagate that information.
    812 
    813             Notification notification;
    814             final boolean showExpandedNotification = true;
    815             if (showExpandedNotification) {
    816                 Intent intent = new Intent(Intent.ACTION_MAIN);
    817                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    818                 intent.setClassName("com.android.phone",
    819                         "com.android.phone.CallFeaturesSetting");
    820 
    821                 notification = new Notification(
    822                         mContext,  // context
    823                         android.R.drawable.stat_sys_phone_call_forward,  // icon
    824                         null, // tickerText
    825                         0,  // The "timestamp" of this notification is meaningless;
    826                             // we only care about whether CFI is currently on or not.
    827                         mContext.getString(R.string.labelCF), // expandedTitle
    828                         mContext.getString(R.string.sum_cfu_enabled_indicator),  // expandedText
    829                         intent // contentIntent
    830                         );
    831 
    832             } else {
    833                 notification = new Notification(
    834                         android.R.drawable.stat_sys_phone_call_forward,  // icon
    835                         null,  // tickerText
    836                         System.currentTimeMillis()  // when
    837                         );
    838             }
    839 
    840             notification.flags |= Notification.FLAG_ONGOING_EVENT;  // also implies FLAG_NO_CLEAR
    841 
    842             mNotificationMgr.notify(
    843                     CALL_FORWARD_NOTIFICATION,
    844                     notification);
    845         } else {
    846             mNotificationMgr.cancel(CALL_FORWARD_NOTIFICATION);
    847         }
    848     }
    849 
    850     /**
    851      * Shows the "data disconnected due to roaming" notification, which
    852      * appears when you lose data connectivity because you're roaming and
    853      * you have the "data roaming" feature turned off.
    854      */
    855     /* package */ void showDataDisconnectedRoaming() {
    856         if (DBG) log("showDataDisconnectedRoaming()...");
    857 
    858         Intent intent = new Intent(mContext,
    859                                    Settings.class);  // "Mobile network settings" screen
    860 
    861         Notification notification = new Notification(
    862                 mContext,  // context
    863                 android.R.drawable.stat_sys_warning,  // icon
    864                 null, // tickerText
    865                 System.currentTimeMillis(),
    866                 mContext.getString(R.string.roaming), // expandedTitle
    867                 mContext.getString(R.string.roaming_reenable_message),  // expandedText
    868                 intent // contentIntent
    869                 );
    870         mNotificationMgr.notify(
    871                 DATA_DISCONNECTED_ROAMING_NOTIFICATION,
    872                 notification);
    873     }
    874 
    875     /**
    876      * Turns off the "data disconnected due to roaming" notification.
    877      */
    878     /* package */ void hideDataDisconnectedRoaming() {
    879         if (DBG) log("hideDataDisconnectedRoaming()...");
    880         mNotificationMgr.cancel(DATA_DISCONNECTED_ROAMING_NOTIFICATION);
    881     }
    882 
    883     /**
    884      * Display the network selection "no service" notification
    885      * @param operator is the numeric operator number
    886      */
    887     private void showNetworkSelection(String operator) {
    888         if (DBG) log("showNetworkSelection(" + operator + ")...");
    889 
    890         String titleText = mContext.getString(
    891                 R.string.notification_network_selection_title);
    892         String expandedText = mContext.getString(
    893                 R.string.notification_network_selection_text, operator);
    894 
    895         Notification notification = new Notification();
    896         notification.icon = com.android.internal.R.drawable.stat_sys_warning;
    897         notification.when = 0;
    898         notification.flags = Notification.FLAG_ONGOING_EVENT;
    899         notification.tickerText = null;
    900 
    901         // create the target network operators settings intent
    902         Intent intent = new Intent(Intent.ACTION_MAIN);
    903         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
    904                 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
    905         // Use NetworkSetting to handle the selection intent
    906         intent.setComponent(new ComponentName("com.android.phone",
    907                 "com.android.phone.NetworkSetting"));
    908         PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
    909 
    910         notification.setLatestEventInfo(mContext, titleText, expandedText, pi);
    911 
    912         mNotificationMgr.notify(SELECTED_OPERATOR_FAIL_NOTIFICATION, notification);
    913     }
    914 
    915     /**
    916      * Turn off the network selection "no service" notification
    917      */
    918     private void cancelNetworkSelection() {
    919         if (DBG) log("cancelNetworkSelection()...");
    920         mNotificationMgr.cancel(SELECTED_OPERATOR_FAIL_NOTIFICATION);
    921     }
    922 
    923     /**
    924      * Update notification about no service of user selected operator
    925      *
    926      * @param serviceState Phone service state
    927      */
    928     void updateNetworkSelection(int serviceState) {
    929         if (mPhone.getPhoneType() == Phone.PHONE_TYPE_GSM) {
    930             // get the shared preference of network_selection.
    931             // empty is auto mode, otherwise it is the operator alpha name
    932             // in case there is no operator name, check the operator numeric
    933             SharedPreferences sp =
    934                     PreferenceManager.getDefaultSharedPreferences(mContext);
    935             String networkSelection =
    936                     sp.getString(PhoneBase.NETWORK_SELECTION_NAME_KEY, "");
    937             if (TextUtils.isEmpty(networkSelection)) {
    938                 networkSelection =
    939                         sp.getString(PhoneBase.NETWORK_SELECTION_KEY, "");
    940             }
    941 
    942             if (DBG) log("updateNetworkSelection()..." + "state = " +
    943                     serviceState + " new network " + networkSelection);
    944 
    945             if (serviceState == ServiceState.STATE_OUT_OF_SERVICE
    946                     && !TextUtils.isEmpty(networkSelection)) {
    947                 if (!mSelectedUnavailableNotify) {
    948                     showNetworkSelection(networkSelection);
    949                     mSelectedUnavailableNotify = true;
    950                 }
    951             } else {
    952                 if (mSelectedUnavailableNotify) {
    953                     cancelNetworkSelection();
    954                     mSelectedUnavailableNotify = false;
    955                 }
    956             }
    957         }
    958     }
    959 
    960     /* package */ void postTransientNotification(int notifyId, CharSequence msg) {
    961         if (mToast != null) {
    962             mToast.cancel();
    963         }
    964 
    965         mToast = Toast.makeText(mContext, msg, Toast.LENGTH_LONG);
    966         mToast.show();
    967     }
    968 
    969     private void log(String msg) {
    970         Log.d(LOG_TAG, msg);
    971     }
    972 }
    973