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.PowerManager;
     33 import android.os.SystemClock;
     34 import android.os.SystemProperties;
     35 import android.preference.PreferenceManager;
     36 import android.provider.CallLog.Calls;
     37 import android.provider.ContactsContract.PhoneLookup;
     38 import android.telephony.PhoneNumberUtils;
     39 import android.telephony.ServiceState;
     40 import android.text.TextUtils;
     41 import android.util.Log;
     42 import android.widget.RemoteViews;
     43 import android.widget.Toast;
     44 
     45 import com.android.internal.telephony.Call;
     46 import com.android.internal.telephony.CallerInfo;
     47 import com.android.internal.telephony.CallerInfoAsyncQuery;
     48 import com.android.internal.telephony.Connection;
     49 import com.android.internal.telephony.Phone;
     50 import com.android.internal.telephony.PhoneBase;
     51 import com.android.internal.telephony.CallManager;
     52 
     53 /**
     54  * NotificationManager-related utility code for the Phone app.
     55  *
     56  * This is a singleton object which acts as the interface to the
     57  * framework's NotificationManager, and is used to display status bar
     58  * icons and control other status bar-related behavior.
     59  *
     60  * @see PhoneApp.notificationMgr
     61  */
     62 public class NotificationMgr implements CallerInfoAsyncQuery.OnQueryCompleteListener{
     63     private static final String LOG_TAG = "NotificationMgr";
     64     private static final boolean DBG =
     65             (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
     66 
     67     private static final String[] CALL_LOG_PROJECTION = new String[] {
     68         Calls._ID,
     69         Calls.NUMBER,
     70         Calls.DATE,
     71         Calls.DURATION,
     72         Calls.TYPE,
     73     };
     74 
     75     // notification types
     76     static final int MISSED_CALL_NOTIFICATION = 1;
     77     static final int IN_CALL_NOTIFICATION = 2;
     78     static final int MMI_NOTIFICATION = 3;
     79     static final int NETWORK_SELECTION_NOTIFICATION = 4;
     80     static final int VOICEMAIL_NOTIFICATION = 5;
     81     static final int CALL_FORWARD_NOTIFICATION = 6;
     82     static final int DATA_DISCONNECTED_ROAMING_NOTIFICATION = 7;
     83     static final int SELECTED_OPERATOR_FAIL_NOTIFICATION = 8;
     84 
     85     /** The singleton NotificationMgr instance. */
     86     private static NotificationMgr sInstance;
     87 
     88     private PhoneApp mApp;
     89     private Phone mPhone;
     90     private CallManager mCM;
     91 
     92     private Context mContext;
     93     private NotificationManager mNotificationManager;
     94     private StatusBarManager mStatusBarManager;
     95     private PowerManager mPowerManager;
     96     private Toast mToast;
     97     private boolean mShowingSpeakerphoneIcon;
     98     private boolean mShowingMuteIcon;
     99 
    100     public StatusBarHelper statusBarHelper;
    101 
    102     // used to track the missed call counter, default to 0.
    103     private int mNumberMissedCalls = 0;
    104 
    105     // Currently-displayed resource IDs for some status bar icons (or zero
    106     // if no notification is active):
    107     private int mInCallResId;
    108 
    109     // used to track the notification of selected network unavailable
    110     private boolean mSelectedUnavailableNotify = false;
    111 
    112     // Retry params for the getVoiceMailNumber() call; see updateMwi().
    113     private static final int MAX_VM_NUMBER_RETRIES = 5;
    114     private static final int VM_NUMBER_RETRY_DELAY_MILLIS = 10000;
    115     private int mVmNumberRetriesRemaining = MAX_VM_NUMBER_RETRIES;
    116 
    117     // Query used to look up caller-id info for the "call log" notification.
    118     private QueryHandler mQueryHandler = null;
    119     private static final int CALL_LOG_TOKEN = -1;
    120     private static final int CONTACT_TOKEN = -2;
    121 
    122     /**
    123      * Private constructor (this is a singleton).
    124      * @see init()
    125      */
    126     private NotificationMgr(PhoneApp app) {
    127         mApp = app;
    128         mContext = app;
    129         mNotificationManager =
    130                 (NotificationManager) app.getSystemService(Context.NOTIFICATION_SERVICE);
    131         mStatusBarManager =
    132                 (StatusBarManager) app.getSystemService(Context.STATUS_BAR_SERVICE);
    133         mPowerManager =
    134                 (PowerManager) app.getSystemService(Context.POWER_SERVICE);
    135         mPhone = app.phone;  // TODO: better style to use mCM.getDefaultPhone() everywhere instead
    136         mCM = app.mCM;
    137         statusBarHelper = new StatusBarHelper();
    138     }
    139 
    140     /**
    141      * Initialize the singleton NotificationMgr instance.
    142      *
    143      * This is only done once, at startup, from PhoneApp.onCreate().
    144      * From then on, the NotificationMgr instance is available via the
    145      * PhoneApp's public "notificationMgr" field, which is why there's no
    146      * getInstance() method here.
    147      */
    148     /* package */ static NotificationMgr init(PhoneApp app) {
    149         synchronized (NotificationMgr.class) {
    150             if (sInstance == null) {
    151                 sInstance = new NotificationMgr(app);
    152                 // Update the notifications that need to be touched at startup.
    153                 sInstance.updateNotificationsAtStartup();
    154             } else {
    155                 Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
    156             }
    157             return sInstance;
    158         }
    159     }
    160 
    161     /**
    162      * Helper class that's a wrapper around the framework's
    163      * StatusBarManager.disable() API.
    164      *
    165      * This class is used to control features like:
    166      *
    167      *   - Disabling the status bar "notification windowshade"
    168      *     while the in-call UI is up
    169      *
    170      *   - Disabling notification alerts (audible or vibrating)
    171      *     while a phone call is active
    172      *
    173      *   - Disabling navigation via the system bar (the "soft buttons" at
    174      *     the bottom of the screen on devices with no hard buttons)
    175      *
    176      * We control these features through a single point of control to make
    177      * sure that the various StatusBarManager.disable() calls don't
    178      * interfere with each other.
    179      */
    180     public class StatusBarHelper {
    181         // Current desired state of status bar / system bar behavior
    182         private boolean mIsNotificationEnabled = true;
    183         private boolean mIsExpandedViewEnabled = true;
    184         private boolean mIsSystemBarNavigationEnabled = true;
    185 
    186         private StatusBarHelper () {
    187         }
    188 
    189         /**
    190          * Enables or disables auditory / vibrational alerts.
    191          *
    192          * (We disable these any time a voice call is active, regardless
    193          * of whether or not the in-call UI is visible.)
    194          */
    195         public void enableNotificationAlerts(boolean enable) {
    196             if (mIsNotificationEnabled != enable) {
    197                 mIsNotificationEnabled = enable;
    198                 updateStatusBar();
    199             }
    200         }
    201 
    202         /**
    203          * Enables or disables the expanded view of the status bar
    204          * (i.e. the ability to pull down the "notification windowshade").
    205          *
    206          * (This feature is disabled by the InCallScreen while the in-call
    207          * UI is active.)
    208          */
    209         public void enableExpandedView(boolean enable) {
    210             if (mIsExpandedViewEnabled != enable) {
    211                 mIsExpandedViewEnabled = enable;
    212                 updateStatusBar();
    213             }
    214         }
    215 
    216         /**
    217          * Enables or disables the navigation via the system bar (the
    218          * "soft buttons" at the bottom of the screen)
    219          *
    220          * (This feature is disabled while an incoming call is ringing,
    221          * because it's easy to accidentally touch the system bar while
    222          * pulling the phone out of your pocket.)
    223          */
    224         public void enableSystemBarNavigation(boolean enable) {
    225             if (mIsSystemBarNavigationEnabled != enable) {
    226                 mIsSystemBarNavigationEnabled = enable;
    227                 updateStatusBar();
    228             }
    229         }
    230 
    231         /**
    232          * Updates the status bar to reflect the current desired state.
    233          */
    234         private void updateStatusBar() {
    235             int state = StatusBarManager.DISABLE_NONE;
    236 
    237             if (!mIsExpandedViewEnabled) {
    238                 state |= StatusBarManager.DISABLE_EXPAND;
    239             }
    240             if (!mIsNotificationEnabled) {
    241                 state |= StatusBarManager.DISABLE_NOTIFICATION_ALERTS;
    242             }
    243             if (!mIsSystemBarNavigationEnabled) {
    244                 // Disable *all* possible navigation via the system bar.
    245                 state |= StatusBarManager.DISABLE_HOME;
    246                 state |= StatusBarManager.DISABLE_RECENT;
    247                 state |= StatusBarManager.DISABLE_BACK;
    248             }
    249 
    250             if (DBG) log("updateStatusBar: state = 0x" + Integer.toHexString(state));
    251             mStatusBarManager.disable(state);
    252         }
    253     }
    254 
    255     /**
    256      * Makes sure phone-related notifications are up to date on a
    257      * freshly-booted device.
    258      */
    259     private void updateNotificationsAtStartup() {
    260         if (DBG) log("updateNotificationsAtStartup()...");
    261 
    262         // instantiate query handler
    263         mQueryHandler = new QueryHandler(mContext.getContentResolver());
    264 
    265         // setup query spec, look for all Missed calls that are new.
    266         StringBuilder where = new StringBuilder("type=");
    267         where.append(Calls.MISSED_TYPE);
    268         where.append(" AND new=1");
    269 
    270         // start the query
    271         if (DBG) log("- start call log query...");
    272         mQueryHandler.startQuery(CALL_LOG_TOKEN, null, Calls.CONTENT_URI,  CALL_LOG_PROJECTION,
    273                 where.toString(), null, Calls.DEFAULT_SORT_ORDER);
    274 
    275         // Update (or cancel) the in-call notification
    276         if (DBG) log("- updating in-call notification at startup...");
    277         updateInCallNotification();
    278 
    279         // Depend on android.app.StatusBarManager to be set to
    280         // disable(DISABLE_NONE) upon startup.  This will be the
    281         // case even if the phone app crashes.
    282     }
    283 
    284     /** The projection to use when querying the phones table */
    285     static final String[] PHONES_PROJECTION = new String[] {
    286         PhoneLookup.NUMBER,
    287         PhoneLookup.DISPLAY_NAME
    288     };
    289 
    290     /**
    291      * Class used to run asynchronous queries to re-populate
    292      * the notifications we care about.
    293      */
    294     private class QueryHandler extends AsyncQueryHandler {
    295 
    296         /**
    297          * Used to store relevant fields for the Missed Call
    298          * notifications.
    299          */
    300         private class NotificationInfo {
    301             public String name;
    302             public String number;
    303             public String label;
    304             public long date;
    305         }
    306 
    307         public QueryHandler(ContentResolver cr) {
    308             super(cr);
    309         }
    310 
    311         /**
    312          * Handles the query results.  There are really 2 steps to this,
    313          * similar to what happens in CallLogActivity.
    314          *  1. Find the list of missed calls
    315          *  2. For each call, run a query to retrieve the caller's name.
    316          */
    317         @Override
    318         protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
    319             // TODO: it would be faster to use a join here, but for the purposes
    320             // of this small record set, it should be ok.
    321 
    322             // Note that CursorJoiner is not useable here because the number
    323             // comparisons are not strictly equals; the comparisons happen in
    324             // the SQL function PHONE_NUMBERS_EQUAL, which is not available for
    325             // the CursorJoiner.
    326 
    327             // Executing our own query is also feasible (with a join), but that
    328             // will require some work (possibly destabilizing) in Contacts
    329             // Provider.
    330 
    331             // At this point, we will execute subqueries on each row just as
    332             // CallLogActivity.java does.
    333             switch (token) {
    334                 case CALL_LOG_TOKEN:
    335                     if (DBG) log("call log query complete.");
    336 
    337                     // initial call to retrieve the call list.
    338                     if (cursor != null) {
    339                         while (cursor.moveToNext()) {
    340                             // for each call in the call log list, create
    341                             // the notification object and query contacts
    342                             NotificationInfo n = getNotificationInfo (cursor);
    343 
    344                             if (DBG) log("query contacts for number: " + n.number);
    345 
    346                             mQueryHandler.startQuery(CONTACT_TOKEN, n,
    347                                     Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, n.number),
    348                                     PHONES_PROJECTION, null, null, PhoneLookup.NUMBER);
    349                         }
    350 
    351                         if (DBG) log("closing call log cursor.");
    352                         cursor.close();
    353                     }
    354                     break;
    355                 case CONTACT_TOKEN:
    356                     if (DBG) log("contact query complete.");
    357 
    358                     // subqueries to get the caller name.
    359                     if ((cursor != null) && (cookie != null)){
    360                         NotificationInfo n = (NotificationInfo) cookie;
    361 
    362                         if (cursor.moveToFirst()) {
    363                             // we have contacts data, get the name.
    364                             if (DBG) log("contact :" + n.name + " found for phone: " + n.number);
    365                             n.name = cursor.getString(
    366                                     cursor.getColumnIndexOrThrow(PhoneLookup.DISPLAY_NAME));
    367                         }
    368 
    369                         // send the notification
    370                         if (DBG) log("sending notification.");
    371                         notifyMissedCall(n.name, n.number, n.label, n.date);
    372 
    373                         if (DBG) log("closing contact cursor.");
    374                         cursor.close();
    375                     }
    376                     break;
    377                 default:
    378             }
    379         }
    380 
    381         /**
    382          * Factory method to generate a NotificationInfo object given a
    383          * cursor from the call log table.
    384          */
    385         private final NotificationInfo getNotificationInfo(Cursor cursor) {
    386             NotificationInfo n = new NotificationInfo();
    387             n.name = null;
    388             n.number = cursor.getString(cursor.getColumnIndexOrThrow(Calls.NUMBER));
    389             n.label = cursor.getString(cursor.getColumnIndexOrThrow(Calls.TYPE));
    390             n.date = cursor.getLong(cursor.getColumnIndexOrThrow(Calls.DATE));
    391 
    392             // make sure we update the number depending upon saved values in
    393             // CallLog.addCall().  If either special values for unknown or
    394             // private number are detected, we need to hand off the message
    395             // to the missed call notification.
    396             if ( (n.number.equals(CallerInfo.UNKNOWN_NUMBER)) ||
    397                  (n.number.equals(CallerInfo.PRIVATE_NUMBER)) ||
    398                  (n.number.equals(CallerInfo.PAYPHONE_NUMBER)) ) {
    399                 n.number = null;
    400             }
    401 
    402             if (DBG) log("NotificationInfo constructed for number: " + n.number);
    403 
    404             return n;
    405         }
    406     }
    407 
    408     /**
    409      * Configures a Notification to emit the blinky green message-waiting/
    410      * missed-call signal.
    411      */
    412     private static void configureLedNotification(Notification note) {
    413         note.flags |= Notification.FLAG_SHOW_LIGHTS;
    414         note.defaults |= Notification.DEFAULT_LIGHTS;
    415     }
    416 
    417     /**
    418      * Displays a notification about a missed call.
    419      *
    420      * @param nameOrNumber either the contact name, or the phone number if no contact
    421      * @param label the label of the number if nameOrNumber is a name, null if it is a number
    422      */
    423     void notifyMissedCall(String name, String number, String label, long date) {
    424         // When the user clicks this notification, we go to the call log.
    425         final Intent callLogIntent = PhoneApp.createCallLogIntent();
    426 
    427         // Never display the missed call notification on non-voice-capable
    428         // devices, even if the device does somehow manage to get an
    429         // incoming call.
    430         if (!PhoneApp.sVoiceCapable) {
    431             if (DBG) log("notifyMissedCall: non-voice-capable device, not posting notification");
    432             return;
    433         }
    434 
    435         // title resource id
    436         int titleResId;
    437         // the text in the notification's line 1 and 2.
    438         String expandedText, callName;
    439 
    440         // increment number of missed calls.
    441         mNumberMissedCalls++;
    442 
    443         // get the name for the ticker text
    444         // i.e. "Missed call from <caller name or number>"
    445         if (name != null && TextUtils.isGraphic(name)) {
    446             callName = name;
    447         } else if (!TextUtils.isEmpty(number)){
    448             callName = number;
    449         } else {
    450             // use "unknown" if the caller is unidentifiable.
    451             callName = mContext.getString(R.string.unknown);
    452         }
    453 
    454         // display the first line of the notification:
    455         // 1 missed call: call name
    456         // more than 1 missed call: <number of calls> + "missed calls"
    457         if (mNumberMissedCalls == 1) {
    458             titleResId = R.string.notification_missedCallTitle;
    459             expandedText = callName;
    460         } else {
    461             titleResId = R.string.notification_missedCallsTitle;
    462             expandedText = mContext.getString(R.string.notification_missedCallsMsg,
    463                     mNumberMissedCalls);
    464         }
    465 
    466         // make the notification
    467         Notification note = new Notification(
    468                 android.R.drawable.stat_notify_missed_call, // icon
    469                 mContext.getString(R.string.notification_missedCallTicker, callName), // tickerText
    470                 date // when
    471                 );
    472         note.setLatestEventInfo(mContext, mContext.getText(titleResId), expandedText,
    473                 PendingIntent.getActivity(mContext, 0, callLogIntent, 0));
    474         note.flags |= Notification.FLAG_AUTO_CANCEL;
    475         // This intent will be called when the notification is dismissed.
    476         // It will take care of clearing the list of missed calls.
    477         note.deleteIntent = createClearMissedCallsIntent();
    478 
    479         configureLedNotification(note);
    480         mNotificationManager.notify(MISSED_CALL_NOTIFICATION, note);
    481     }
    482 
    483     /** Returns an intent to be invoked when the missed call notification is cleared. */
    484     private PendingIntent createClearMissedCallsIntent() {
    485         Intent intent = new Intent(mContext, ClearMissedCallsService.class);
    486         intent.setAction(ClearMissedCallsService.ACTION_CLEAR_MISSED_CALLS);
    487         return PendingIntent.getService(mContext, 0, intent, 0);
    488     }
    489 
    490     /**
    491      * Cancels the "missed call" notification.
    492      *
    493      * @see ITelephony.cancelMissedCallsNotification()
    494      */
    495     void cancelMissedCallNotification() {
    496         // reset the number of missed calls to 0.
    497         mNumberMissedCalls = 0;
    498         mNotificationManager.cancel(MISSED_CALL_NOTIFICATION);
    499     }
    500 
    501     private void notifySpeakerphone() {
    502         if (!mShowingSpeakerphoneIcon) {
    503             mStatusBarManager.setIcon("speakerphone", android.R.drawable.stat_sys_speakerphone, 0,
    504                     mContext.getString(R.string.accessibility_speakerphone_enabled));
    505             mShowingSpeakerphoneIcon = true;
    506         }
    507     }
    508 
    509     private void cancelSpeakerphone() {
    510         if (mShowingSpeakerphoneIcon) {
    511             mStatusBarManager.removeIcon("speakerphone");
    512             mShowingSpeakerphoneIcon = false;
    513         }
    514     }
    515 
    516     /**
    517      * Shows or hides the "speakerphone" notification in the status bar,
    518      * based on the actual current state of the speaker.
    519      *
    520      * If you already know the current speaker state (e.g. if you just
    521      * called AudioManager.setSpeakerphoneOn() yourself) then you should
    522      * directly call {@link updateSpeakerNotification(boolean)} instead.
    523      *
    524      * (But note that the status bar icon is *never* shown while the in-call UI
    525      * is active; it only appears if you bail out to some other activity.)
    526      */
    527     public void updateSpeakerNotification() {
    528         AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
    529         boolean showNotification =
    530                 (mPhone.getState() == Phone.State.OFFHOOK) && audioManager.isSpeakerphoneOn();
    531 
    532         if (DBG) log(showNotification
    533                      ? "updateSpeakerNotification: speaker ON"
    534                      : "updateSpeakerNotification: speaker OFF (or not offhook)");
    535 
    536         updateSpeakerNotification(showNotification);
    537     }
    538 
    539     /**
    540      * Shows or hides the "speakerphone" notification in the status bar.
    541      *
    542      * @param showNotification if true, call notifySpeakerphone();
    543      *                         if false, call cancelSpeakerphone().
    544      *
    545      * Use {@link updateSpeakerNotification()} to update the status bar
    546      * based on the actual current state of the speaker.
    547      *
    548      * (But note that the status bar icon is *never* shown while the in-call UI
    549      * is active; it only appears if you bail out to some other activity.)
    550      */
    551     public void updateSpeakerNotification(boolean showNotification) {
    552         if (DBG) log("updateSpeakerNotification(" + showNotification + ")...");
    553 
    554         // Regardless of the value of the showNotification param, suppress
    555         // the status bar icon if the the InCallScreen is the foreground
    556         // activity, since the in-call UI already provides an onscreen
    557         // indication of the speaker state.  (This reduces clutter in the
    558         // status bar.)
    559         if (mApp.isShowingCallScreen()) {
    560             cancelSpeakerphone();
    561             return;
    562         }
    563 
    564         if (showNotification) {
    565             notifySpeakerphone();
    566         } else {
    567             cancelSpeakerphone();
    568         }
    569     }
    570 
    571     private void notifyMute() {
    572         if (!mShowingMuteIcon) {
    573             mStatusBarManager.setIcon("mute", android.R.drawable.stat_notify_call_mute, 0,
    574                     mContext.getString(R.string.accessibility_call_muted));
    575             mShowingMuteIcon = true;
    576         }
    577     }
    578 
    579     private void cancelMute() {
    580         if (mShowingMuteIcon) {
    581             mStatusBarManager.removeIcon("mute");
    582             mShowingMuteIcon = false;
    583         }
    584     }
    585 
    586     /**
    587      * Shows or hides the "mute" notification in the status bar,
    588      * based on the current mute state of the Phone.
    589      *
    590      * (But note that the status bar icon is *never* shown while the in-call UI
    591      * is active; it only appears if you bail out to some other activity.)
    592      */
    593     void updateMuteNotification() {
    594         // Suppress the status bar icon if the the InCallScreen is the
    595         // foreground activity, since the in-call UI already provides an
    596         // onscreen indication of the mute state.  (This reduces clutter
    597         // in the status bar.)
    598         if (mApp.isShowingCallScreen()) {
    599             cancelMute();
    600             return;
    601         }
    602 
    603         if ((mCM.getState() == Phone.State.OFFHOOK) && PhoneUtils.getMute()) {
    604             if (DBG) log("updateMuteNotification: MUTED");
    605             notifyMute();
    606         } else {
    607             if (DBG) log("updateMuteNotification: not muted (or not offhook)");
    608             cancelMute();
    609         }
    610     }
    611 
    612     /**
    613      * Updates the phone app's status bar notification based on the
    614      * current telephony state, or cancels the notification if the phone
    615      * is totally idle.
    616      *
    617      * This method will never actually launch the incoming-call UI.
    618      * (Use updateNotificationAndLaunchIncomingCallUi() for that.)
    619      */
    620     public void updateInCallNotification() {
    621         // allowFullScreenIntent=false means *don't* allow the incoming
    622         // call UI to be launched.
    623         updateInCallNotification(false);
    624     }
    625 
    626     /**
    627      * Updates the phone app's status bar notification *and* launches the
    628      * incoming call UI in response to a new incoming call.
    629      *
    630      * This is just like updateInCallNotification(), with one exception:
    631      * If an incoming call is ringing (or call-waiting), the notification
    632      * will also include a "fullScreenIntent" that will cause the
    633      * InCallScreen to be launched immediately, unless the current
    634      * foreground activity is marked as "immersive".
    635      *
    636      * (This is the mechanism that actually brings up the incoming call UI
    637      * when we receive a "new ringing connection" event from the telephony
    638      * layer.)
    639      *
    640      * Watch out: this method should ONLY be called directly from the code
    641      * path in CallNotifier that handles the "new ringing connection"
    642      * event from the telephony layer.  All other places that update the
    643      * in-call notification (like for phone state changes) should call
    644      * updateInCallNotification() instead.  (This ensures that we don't
    645      * end up launching the InCallScreen multiple times for a single
    646      * incoming call, which could cause slow responsiveness and/or visible
    647      * glitches.)
    648      *
    649      * Also note that this method is safe to call even if the phone isn't
    650      * actually ringing (or, more likely, if an incoming call *was*
    651      * ringing briefly but then disconnected).  In that case, we'll simply
    652      * update or cancel the in-call notification based on the current
    653      * phone state.
    654      *
    655      * @see updateInCallNotification()
    656      */
    657     public void updateNotificationAndLaunchIncomingCallUi() {
    658         // Set allowFullScreenIntent=true to indicate that we *should*
    659         // launch the incoming call UI if necessary.
    660         updateInCallNotification(true);
    661     }
    662 
    663     /**
    664      * Helper method for updateInCallNotification() and
    665      * updateNotificationAndLaunchIncomingCallUi(): Update the phone app's
    666      * status bar notification based on the current telephony state, or
    667      * cancels the notification if the phone is totally idle.
    668      *
    669      * @param allowLaunchInCallScreen If true, *and* an incoming call is
    670      *   ringing, the notification will include a "fullScreenIntent"
    671      *   pointing at the InCallScreen (which will cause the InCallScreen
    672      *   to be launched.)
    673      *   Watch out: This should be set to true *only* when directly
    674      *   handling the "new ringing connection" event from the telephony
    675      *   layer (see updateNotificationAndLaunchIncomingCallUi().)
    676      */
    677     private void updateInCallNotification(boolean allowFullScreenIntent) {
    678         int resId;
    679         if (DBG) log("updateInCallNotification(allowFullScreenIntent = "
    680                      + allowFullScreenIntent + ")...");
    681 
    682         // Never display the "ongoing call" notification on
    683         // non-voice-capable devices, even if the phone is actually
    684         // offhook (like during a non-interactive OTASP call.)
    685         if (!PhoneApp.sVoiceCapable) {
    686             if (DBG) log("- non-voice-capable device; suppressing notification.");
    687             return;
    688         }
    689 
    690         // If the phone is idle, completely clean up all call-related
    691         // notifications.
    692         if (mCM.getState() == Phone.State.IDLE) {
    693             cancelInCall();
    694             cancelMute();
    695             cancelSpeakerphone();
    696             return;
    697         }
    698 
    699         final boolean hasRingingCall = mCM.hasActiveRingingCall();
    700         final boolean hasActiveCall = mCM.hasActiveFgCall();
    701         final boolean hasHoldingCall = mCM.hasActiveBgCall();
    702         if (DBG) {
    703             log("  - hasRingingCall = " + hasRingingCall);
    704             log("  - hasActiveCall = " + hasActiveCall);
    705             log("  - hasHoldingCall = " + hasHoldingCall);
    706         }
    707 
    708         // Suppress the in-call notification if the InCallScreen is the
    709         // foreground activity, since it's already obvious that you're on a
    710         // call.  (The status bar icon is needed only if you navigate *away*
    711         // from the in-call UI.)
    712         boolean suppressNotification = mApp.isShowingCallScreen();
    713         // if (DBG) log("- suppressNotification: initial value: " + suppressNotification);
    714 
    715         // Additionally, suppress the notification if the screen is off.
    716         // (Of course no UI is visible at this point(!) -- we're doing
    717         // this purely to avoid a brief flicker of the icon in the status
    718         // bar when the screen turns back on (due to the prox sensor, for
    719         // example) while still on the InCallScreen.)
    720         boolean isScreenOn = mPowerManager.isScreenOn();
    721         // if (DBG) log("  - suppressNotification: isScreenOn = " + isScreenOn);
    722         if (!isScreenOn) suppressNotification = true;
    723 
    724         // ...except for a couple of cases where we *never* suppress the
    725         // notification:
    726         //
    727         //   - If there's an incoming ringing call: always show the
    728         //     notification, since the in-call notification is what actually
    729         //     launches the incoming call UI in the first place (see
    730         //     notification.fullScreenIntent below.)  This makes sure that we'll
    731         //     correctly handle the case where a new incoming call comes in but
    732         //     the InCallScreen is already in the foreground.
    733         if (hasRingingCall) suppressNotification = false;
    734 
    735         //   - If "voice privacy" mode is active: always show the notification,
    736         //     since that's the only "voice privacy" indication we have.
    737         boolean enhancedVoicePrivacy = mApp.notifier.getVoicePrivacyState();
    738         // if (DBG) log("updateInCallNotification: enhancedVoicePrivacy = " + enhancedVoicePrivacy);
    739         if (enhancedVoicePrivacy) suppressNotification = false;
    740 
    741         if (suppressNotification) {
    742             if (DBG) log("- suppressNotification = true; reducing clutter in status bar...");
    743             cancelInCall();
    744             // Suppress the mute and speaker status bar icons too
    745             // (also to reduce clutter in the status bar.)
    746             cancelSpeakerphone();
    747             cancelMute();
    748             return;
    749         }
    750 
    751         // Display the appropriate icon in the status bar,
    752         // based on the current phone and/or bluetooth state.
    753 
    754         if (hasRingingCall) {
    755             // There's an incoming ringing call.
    756             resId = R.drawable.stat_sys_phone_call_ringing;
    757         } else if (!hasActiveCall && hasHoldingCall) {
    758             // There's only one call, and it's on hold.
    759             if (enhancedVoicePrivacy) {
    760                 resId = R.drawable.stat_sys_vp_phone_call_on_hold;
    761             } else {
    762                 resId = R.drawable.stat_sys_phone_call_on_hold;
    763             }
    764         } else if (mApp.showBluetoothIndication()) {
    765             // Bluetooth is active.
    766             if (enhancedVoicePrivacy) {
    767                 resId = R.drawable.stat_sys_vp_phone_call_bluetooth;
    768             } else {
    769                 resId = R.drawable.stat_sys_phone_call_bluetooth;
    770             }
    771         } else {
    772             if (enhancedVoicePrivacy) {
    773                 resId = R.drawable.stat_sys_vp_phone_call;
    774             } else {
    775                 resId = R.drawable.stat_sys_phone_call;
    776             }
    777         }
    778 
    779         // Note we can't just bail out now if (resId == mInCallResId),
    780         // since even if the status icon hasn't changed, some *other*
    781         // notification-related info may be different from the last time
    782         // we were here (like the caller-id info of the foreground call,
    783         // if the user swapped calls...)
    784 
    785         if (DBG) log("- Updating status bar icon: resId = " + resId);
    786         mInCallResId = resId;
    787 
    788         // The icon in the expanded view is the same as in the status bar.
    789         int expandedViewIcon = mInCallResId;
    790 
    791         // Even if both lines are in use, we only show a single item in
    792         // the expanded Notifications UI.  It's labeled "Ongoing call"
    793         // (or "On hold" if there's only one call, and it's on hold.)
    794         // Also, we don't have room to display caller-id info from two
    795         // different calls.  So if both lines are in use, display info
    796         // from the foreground call.  And if there's a ringing call,
    797         // display that regardless of the state of the other calls.
    798 
    799         Call currentCall;
    800         if (hasRingingCall) {
    801             currentCall = mCM.getFirstActiveRingingCall();
    802         } else if (hasActiveCall) {
    803             currentCall = mCM.getActiveFgCall();
    804         } else {
    805             currentCall = mCM.getFirstActiveBgCall();
    806         }
    807         Connection currentConn = currentCall.getEarliestConnection();
    808 
    809         Notification notification = new Notification();
    810         notification.icon = mInCallResId;
    811         notification.flags |= Notification.FLAG_ONGOING_EVENT;
    812 
    813         // PendingIntent that can be used to launch the InCallScreen.  The
    814         // system fires off this intent if the user pulls down the windowshade
    815         // and clicks the notification's expanded view.  It's also used to
    816         // launch the InCallScreen immediately when when there's an incoming
    817         // call (see the "fullScreenIntent" field below).
    818         PendingIntent inCallPendingIntent =
    819                 PendingIntent.getActivity(mContext, 0,
    820                                           PhoneApp.createInCallIntent(), 0);
    821         notification.contentIntent = inCallPendingIntent;
    822 
    823         // When expanded, the "Ongoing call" notification is (visually)
    824         // different from most other Notifications, so we need to use a
    825         // custom view hierarchy.
    826         // Our custom view, which includes an icon (either "ongoing call" or
    827         // "on hold") and 2 lines of text: (1) the label (either "ongoing
    828         // call" with time counter, or "on hold), and (2) the compact name of
    829         // the current Connection.
    830         RemoteViews contentView = new RemoteViews(mContext.getPackageName(),
    831                                                    R.layout.ongoing_call_notification);
    832         contentView.setImageViewResource(R.id.icon, expandedViewIcon);
    833 
    834         // if the connection is valid, then build what we need for the
    835         // first line of notification information, and start the chronometer.
    836         // Otherwise, don't bother and just stick with line 2.
    837         if (currentConn != null) {
    838             // Determine the "start time" of the current connection, in terms
    839             // of the SystemClock.elapsedRealtime() timebase (which is what
    840             // the Chronometer widget needs.)
    841             //   We can't use currentConn.getConnectTime(), because (1) that's
    842             // in the currentTimeMillis() time base, and (2) it's zero when
    843             // the phone first goes off hook, since the getConnectTime counter
    844             // doesn't start until the DIALING -> ACTIVE transition.
    845             //   Instead we start with the current connection's duration,
    846             // and translate that into the elapsedRealtime() timebase.
    847             long callDurationMsec = currentConn.getDurationMillis();
    848             long chronometerBaseTime = SystemClock.elapsedRealtime() - callDurationMsec;
    849 
    850             // Line 1 of the expanded view (in bold text):
    851             String expandedViewLine1;
    852             if (hasRingingCall) {
    853                 // Incoming call is ringing.
    854                 // Note this isn't a format string!  (We want "Incoming call"
    855                 // here, not "Incoming call (1:23)".)  But that's OK; if you
    856                 // call String.format() with more arguments than format
    857                 // specifiers, the extra arguments are ignored.
    858                 expandedViewLine1 = mContext.getString(R.string.notification_incoming_call);
    859             } else if (hasHoldingCall && !hasActiveCall) {
    860                 // Only one call, and it's on hold.
    861                 // Note this isn't a format string either (see comment above.)
    862                 expandedViewLine1 = mContext.getString(R.string.notification_on_hold);
    863             } else {
    864                 // Normal ongoing call.
    865                 // Format string with a "%s" where the current call time should go.
    866                 expandedViewLine1 = mContext.getString(R.string.notification_ongoing_call_format);
    867             }
    868 
    869             if (DBG) log("- Updating expanded view: line 1 '" + /*expandedViewLine1*/ "xxxxxxx" + "'");
    870 
    871             // Text line #1 is actually a Chronometer, not a plain TextView.
    872             // We format the elapsed time of the current call into a line like
    873             // "Ongoing call (01:23)".
    874             contentView.setChronometer(R.id.text1,
    875                                        chronometerBaseTime,
    876                                        expandedViewLine1,
    877                                        true);
    878         } else if (DBG) {
    879             Log.w(LOG_TAG, "updateInCallNotification: null connection, can't set exp view line 1.");
    880         }
    881 
    882         // display conference call string if this call is a conference
    883         // call, otherwise display the connection information.
    884 
    885         // Line 2 of the expanded view (smaller text).  This is usually a
    886         // contact name or phone number.
    887         String expandedViewLine2 = "";
    888         // TODO: it may not make sense for every point to make separate
    889         // checks for isConferenceCall, so we need to think about
    890         // possibly including this in startGetCallerInfo or some other
    891         // common point.
    892         if (PhoneUtils.isConferenceCall(currentCall)) {
    893             // if this is a conference call, just use that as the caller name.
    894             expandedViewLine2 = mContext.getString(R.string.card_title_conf_call);
    895         } else {
    896             // If necessary, start asynchronous query to do the caller-id lookup.
    897             PhoneUtils.CallerInfoToken cit =
    898                 PhoneUtils.startGetCallerInfo(mContext, currentCall, this, this);
    899             expandedViewLine2 = PhoneUtils.getCompactNameFromCallerInfo(cit.currentInfo, mContext);
    900             // Note: For an incoming call, the very first time we get here we
    901             // won't have a contact name yet, since we only just started the
    902             // caller-id query.  So expandedViewLine2 will start off as a raw
    903             // phone number, but we'll update it very quickly when the query
    904             // completes (see onQueryComplete() below.)
    905         }
    906 
    907         if (DBG) log("- Updating expanded view: line 2 '" + /*expandedViewLine2*/ "xxxxxxx" + "'");
    908         contentView.setTextViewText(R.id.title, expandedViewLine2);
    909         notification.contentView = contentView;
    910 
    911         // TODO: We also need to *update* this notification in some cases,
    912         // like when a call ends on one line but the other is still in use
    913         // (ie. make sure the caller info here corresponds to the active
    914         // line), and maybe even when the user swaps calls (ie. if we only
    915         // show info here for the "current active call".)
    916 
    917         // Activate a couple of special Notification features if an
    918         // incoming call is ringing:
    919         if (hasRingingCall) {
    920             if (DBG) log("- Using hi-pri notification for ringing call!");
    921 
    922             // This is a high-priority event that should be shown even if the
    923             // status bar is hidden or if an immersive activity is running.
    924             notification.flags |= Notification.FLAG_HIGH_PRIORITY;
    925 
    926             // If an immersive activity is running, we have room for a single
    927             // line of text in the small notification popup window.
    928             // We use expandedViewLine2 for this (i.e. the name or number of
    929             // the incoming caller), since that's more relevant than
    930             // expandedViewLine1 (which is something generic like "Incoming
    931             // call".)
    932             notification.tickerText = expandedViewLine2;
    933 
    934             if (allowFullScreenIntent) {
    935                 // Ok, we actually want to launch the incoming call
    936                 // UI at this point (in addition to simply posting a notification
    937                 // to the status bar).  Setting fullScreenIntent will cause
    938                 // the InCallScreen to be launched immediately *unless* the
    939                 // current foreground activity is marked as "immersive".
    940                 if (DBG) log("- Setting fullScreenIntent: " + inCallPendingIntent);
    941                 notification.fullScreenIntent = inCallPendingIntent;
    942 
    943                 // Ugly hack alert:
    944                 //
    945                 // The NotificationManager has the (undocumented) behavior
    946                 // that it will *ignore* the fullScreenIntent field if you
    947                 // post a new Notification that matches the ID of one that's
    948                 // already active.  Unfortunately this is exactly what happens
    949                 // when you get an incoming call-waiting call:  the
    950                 // "ongoing call" notification is already visible, so the
    951                 // InCallScreen won't get launched in this case!
    952                 // (The result: if you bail out of the in-call UI while on a
    953                 // call and then get a call-waiting call, the incoming call UI
    954                 // won't come up automatically.)
    955                 //
    956                 // The workaround is to just notice this exact case (this is a
    957                 // call-waiting call *and* the InCallScreen is not in the
    958                 // foreground) and manually cancel the in-call notification
    959                 // before (re)posting it.
    960                 //
    961                 // TODO: there should be a cleaner way of avoiding this
    962                 // problem (see discussion in bug 3184149.)
    963                 Call ringingCall = mCM.getFirstActiveRingingCall();
    964                 if ((ringingCall.getState() == Call.State.WAITING) && !mApp.isShowingCallScreen()) {
    965                     Log.i(LOG_TAG, "updateInCallNotification: call-waiting! force relaunch...");
    966                     // Cancel the IN_CALL_NOTIFICATION immediately before
    967                     // (re)posting it; this seems to force the
    968                     // NotificationManager to launch the fullScreenIntent.
    969                     mNotificationManager.cancel(IN_CALL_NOTIFICATION);
    970                 }
    971             }
    972         }
    973 
    974         if (DBG) log("Notifying IN_CALL_NOTIFICATION: " + notification);
    975         mNotificationManager.notify(IN_CALL_NOTIFICATION,
    976                                 notification);
    977 
    978         // Finally, refresh the mute and speakerphone notifications (since
    979         // some phone state changes can indirectly affect the mute and/or
    980         // speaker state).
    981         updateSpeakerNotification();
    982         updateMuteNotification();
    983     }
    984 
    985     /**
    986      * Implemented for CallerInfoAsyncQuery.OnQueryCompleteListener interface.
    987      * refreshes the contentView when called.
    988      */
    989     public void onQueryComplete(int token, Object cookie, CallerInfo ci){
    990         if (DBG) log("CallerInfo query complete (for NotificationMgr), "
    991                      + "updating in-call notification..");
    992         if (DBG) log("- cookie: " + cookie);
    993         if (DBG) log("- ci: " + ci);
    994 
    995         if (cookie == this) {
    996             // Ok, this is the caller-id query we fired off in
    997             // updateInCallNotification(), presumably when an incoming call
    998             // first appeared.  If the caller-id info matched any contacts,
    999             // compactName should now be a real person name rather than a raw
   1000             // phone number:
   1001             if (DBG) log("- compactName is now: "
   1002                          + PhoneUtils.getCompactNameFromCallerInfo(ci, mContext));
   1003 
   1004             // Now that our CallerInfo object has been fully filled-in,
   1005             // refresh the in-call notification.
   1006             if (DBG) log("- updating notification after query complete...");
   1007             updateInCallNotification();
   1008         } else {
   1009             Log.w(LOG_TAG, "onQueryComplete: caller-id query from unknown source! "
   1010                   + "cookie = " + cookie);
   1011         }
   1012     }
   1013 
   1014     /**
   1015      * Take down the in-call notification.
   1016      * @see updateInCallNotification()
   1017      */
   1018     private void cancelInCall() {
   1019         if (DBG) log("cancelInCall()...");
   1020         mNotificationManager.cancel(IN_CALL_NOTIFICATION);
   1021         mInCallResId = 0;
   1022     }
   1023 
   1024     /**
   1025      * Completely take down the in-call notification *and* the mute/speaker
   1026      * notifications as well, to indicate that the phone is now idle.
   1027      */
   1028     /* package */ void cancelCallInProgressNotifications() {
   1029         if (DBG) log("cancelCallInProgressNotifications()...");
   1030         if (mInCallResId == 0) {
   1031             return;
   1032         }
   1033 
   1034         if (DBG) log("cancelCallInProgressNotifications: " + mInCallResId);
   1035         cancelInCall();
   1036         cancelMute();
   1037         cancelSpeakerphone();
   1038     }
   1039 
   1040     /**
   1041      * Updates the message waiting indicator (voicemail) notification.
   1042      *
   1043      * @param visible true if there are messages waiting
   1044      */
   1045     /* package */ void updateMwi(boolean visible) {
   1046         if (DBG) log("updateMwi(): " + visible);
   1047         if (visible) {
   1048             int resId = android.R.drawable.stat_notify_voicemail;
   1049 
   1050             // This Notification can get a lot fancier once we have more
   1051             // information about the current voicemail messages.
   1052             // (For example, the current voicemail system can't tell
   1053             // us the caller-id or timestamp of a message, or tell us the
   1054             // message count.)
   1055 
   1056             // But for now, the UI is ultra-simple: if the MWI indication
   1057             // is supposed to be visible, just show a single generic
   1058             // notification.
   1059 
   1060             String notificationTitle = mContext.getString(R.string.notification_voicemail_title);
   1061             String vmNumber = mPhone.getVoiceMailNumber();
   1062             if (DBG) log("- got vm number: '" + vmNumber + "'");
   1063 
   1064             // Watch out: vmNumber may be null, for two possible reasons:
   1065             //
   1066             //   (1) This phone really has no voicemail number
   1067             //
   1068             //   (2) This phone *does* have a voicemail number, but
   1069             //       the SIM isn't ready yet.
   1070             //
   1071             // Case (2) *does* happen in practice if you have voicemail
   1072             // messages when the device first boots: we get an MWI
   1073             // notification as soon as we register on the network, but the
   1074             // SIM hasn't finished loading yet.
   1075             //
   1076             // So handle case (2) by retrying the lookup after a short
   1077             // delay.
   1078 
   1079             if ((vmNumber == null) && !mPhone.getIccRecordsLoaded()) {
   1080                 if (DBG) log("- Null vm number: SIM records not loaded (yet)...");
   1081 
   1082                 // TODO: rather than retrying after an arbitrary delay, it
   1083                 // would be cleaner to instead just wait for a
   1084                 // SIM_RECORDS_LOADED notification.
   1085                 // (Unfortunately right now there's no convenient way to
   1086                 // get that notification in phone app code.  We'd first
   1087                 // want to add a call like registerForSimRecordsLoaded()
   1088                 // to Phone.java and GSMPhone.java, and *then* we could
   1089                 // listen for that in the CallNotifier class.)
   1090 
   1091                 // Limit the number of retries (in case the SIM is broken
   1092                 // or missing and can *never* load successfully.)
   1093                 if (mVmNumberRetriesRemaining-- > 0) {
   1094                     if (DBG) log("  - Retrying in " + VM_NUMBER_RETRY_DELAY_MILLIS + " msec...");
   1095                     mApp.notifier.sendMwiChangedDelayed(VM_NUMBER_RETRY_DELAY_MILLIS);
   1096                     return;
   1097                 } else {
   1098                     Log.w(LOG_TAG, "NotificationMgr.updateMwi: getVoiceMailNumber() failed after "
   1099                           + MAX_VM_NUMBER_RETRIES + " retries; giving up.");
   1100                     // ...and continue with vmNumber==null, just as if the
   1101                     // phone had no VM number set up in the first place.
   1102                 }
   1103             }
   1104 
   1105             if (TelephonyCapabilities.supportsVoiceMessageCount(mPhone)) {
   1106                 int vmCount = mPhone.getVoiceMessageCount();
   1107                 String titleFormat = mContext.getString(R.string.notification_voicemail_title_count);
   1108                 notificationTitle = String.format(titleFormat, vmCount);
   1109             }
   1110 
   1111             String notificationText;
   1112             if (TextUtils.isEmpty(vmNumber)) {
   1113                 notificationText = mContext.getString(
   1114                         R.string.notification_voicemail_no_vm_number);
   1115             } else {
   1116                 notificationText = String.format(
   1117                         mContext.getString(R.string.notification_voicemail_text_format),
   1118                         PhoneNumberUtils.formatNumber(vmNumber));
   1119             }
   1120 
   1121             Intent intent = new Intent(Intent.ACTION_CALL,
   1122                     Uri.fromParts(Constants.SCHEME_VOICEMAIL, "", null));
   1123             PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
   1124 
   1125             Notification notification = new Notification(
   1126                     resId,  // icon
   1127                     null, // tickerText
   1128                     System.currentTimeMillis()  // Show the time the MWI notification came in,
   1129                                                 // since we don't know the actual time of the
   1130                                                 // most recent voicemail message
   1131                     );
   1132             notification.setLatestEventInfo(
   1133                     mContext,  // context
   1134                     notificationTitle,  // contentTitle
   1135                     notificationText,  // contentText
   1136                     pendingIntent  // contentIntent
   1137                     );
   1138             notification.defaults |= Notification.DEFAULT_SOUND;
   1139 
   1140             SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
   1141             String vibrateWhen = prefs.getString(
   1142                     CallFeaturesSetting.BUTTON_VOICEMAIL_NOTIFICATION_VIBRATE_WHEN_KEY, "never");
   1143             boolean vibrateAlways = vibrateWhen.equals("always");
   1144             boolean vibrateSilent = vibrateWhen.equals("silent");
   1145             AudioManager audioManager =
   1146                     (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
   1147             boolean nowSilent = audioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE;
   1148             if (vibrateAlways || (vibrateSilent && nowSilent)) {
   1149                 notification.defaults |= Notification.DEFAULT_VIBRATE;
   1150             }
   1151 
   1152             notification.flags |= Notification.FLAG_NO_CLEAR;
   1153             configureLedNotification(notification);
   1154             mNotificationManager.notify(VOICEMAIL_NOTIFICATION, notification);
   1155         } else {
   1156             mNotificationManager.cancel(VOICEMAIL_NOTIFICATION);
   1157         }
   1158     }
   1159 
   1160     /**
   1161      * Updates the message call forwarding indicator notification.
   1162      *
   1163      * @param visible true if there are messages waiting
   1164      */
   1165     /* package */ void updateCfi(boolean visible) {
   1166         if (DBG) log("updateCfi(): " + visible);
   1167         if (visible) {
   1168             // If Unconditional Call Forwarding (forward all calls) for VOICE
   1169             // is enabled, just show a notification.  We'll default to expanded
   1170             // view for now, so the there is less confusion about the icon.  If
   1171             // it is deemed too weird to have CF indications as expanded views,
   1172             // then we'll flip the flag back.
   1173 
   1174             // TODO: We may want to take a look to see if the notification can
   1175             // display the target to forward calls to.  This will require some
   1176             // effort though, since there are multiple layers of messages that
   1177             // will need to propagate that information.
   1178 
   1179             Notification notification;
   1180             final boolean showExpandedNotification = true;
   1181             if (showExpandedNotification) {
   1182                 Intent intent = new Intent(Intent.ACTION_MAIN);
   1183                 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
   1184                 intent.setClassName("com.android.phone",
   1185                         "com.android.phone.CallFeaturesSetting");
   1186 
   1187                 notification = new Notification(
   1188                         R.drawable.stat_sys_phone_call_forward,  // icon
   1189                         null, // tickerText
   1190                         0); // The "timestamp" of this notification is meaningless;
   1191                             // we only care about whether CFI is currently on or not.
   1192                 notification.setLatestEventInfo(
   1193                         mContext, // context
   1194                         mContext.getString(R.string.labelCF), // expandedTitle
   1195                         mContext.getString(R.string.sum_cfu_enabled_indicator), // expandedText
   1196                         PendingIntent.getActivity(mContext, 0, intent, 0)); // contentIntent
   1197             } else {
   1198                 notification = new Notification(
   1199                         R.drawable.stat_sys_phone_call_forward,  // icon
   1200                         null,  // tickerText
   1201                         System.currentTimeMillis()  // when
   1202                         );
   1203             }
   1204 
   1205             notification.flags |= Notification.FLAG_ONGOING_EVENT;  // also implies FLAG_NO_CLEAR
   1206 
   1207             mNotificationManager.notify(
   1208                     CALL_FORWARD_NOTIFICATION,
   1209                     notification);
   1210         } else {
   1211             mNotificationManager.cancel(CALL_FORWARD_NOTIFICATION);
   1212         }
   1213     }
   1214 
   1215     /**
   1216      * Shows the "data disconnected due to roaming" notification, which
   1217      * appears when you lose data connectivity because you're roaming and
   1218      * you have the "data roaming" feature turned off.
   1219      */
   1220     /* package */ void showDataDisconnectedRoaming() {
   1221         if (DBG) log("showDataDisconnectedRoaming()...");
   1222 
   1223         Intent intent = new Intent(mContext,
   1224                 com.android.phone.Settings.class);  // "Mobile network settings" screen / dialog
   1225 
   1226         Notification notification = new Notification(
   1227                 android.R.drawable.stat_sys_warning, // icon
   1228                 null, // tickerText
   1229                 System.currentTimeMillis());
   1230         notification.setLatestEventInfo(
   1231                 mContext, // Context
   1232                 mContext.getString(R.string.roaming), // expandedTitle
   1233                 mContext.getString(R.string.roaming_reenable_message), // expandedText
   1234                 PendingIntent.getActivity(mContext, 0, intent, 0)); // contentIntent
   1235 
   1236         mNotificationManager.notify(
   1237                 DATA_DISCONNECTED_ROAMING_NOTIFICATION,
   1238                 notification);
   1239     }
   1240 
   1241     /**
   1242      * Turns off the "data disconnected due to roaming" notification.
   1243      */
   1244     /* package */ void hideDataDisconnectedRoaming() {
   1245         if (DBG) log("hideDataDisconnectedRoaming()...");
   1246         mNotificationManager.cancel(DATA_DISCONNECTED_ROAMING_NOTIFICATION);
   1247     }
   1248 
   1249     /**
   1250      * Display the network selection "no service" notification
   1251      * @param operator is the numeric operator number
   1252      */
   1253     private void showNetworkSelection(String operator) {
   1254         if (DBG) log("showNetworkSelection(" + operator + ")...");
   1255 
   1256         String titleText = mContext.getString(
   1257                 R.string.notification_network_selection_title);
   1258         String expandedText = mContext.getString(
   1259                 R.string.notification_network_selection_text, operator);
   1260 
   1261         Notification notification = new Notification();
   1262         notification.icon = android.R.drawable.stat_sys_warning;
   1263         notification.when = 0;
   1264         notification.flags = Notification.FLAG_ONGOING_EVENT;
   1265         notification.tickerText = null;
   1266 
   1267         // create the target network operators settings intent
   1268         Intent intent = new Intent(Intent.ACTION_MAIN);
   1269         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
   1270                 Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
   1271         // Use NetworkSetting to handle the selection intent
   1272         intent.setComponent(new ComponentName("com.android.phone",
   1273                 "com.android.phone.NetworkSetting"));
   1274         PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
   1275 
   1276         notification.setLatestEventInfo(mContext, titleText, expandedText, pi);
   1277 
   1278         mNotificationManager.notify(SELECTED_OPERATOR_FAIL_NOTIFICATION, notification);
   1279     }
   1280 
   1281     /**
   1282      * Turn off the network selection "no service" notification
   1283      */
   1284     private void cancelNetworkSelection() {
   1285         if (DBG) log("cancelNetworkSelection()...");
   1286         mNotificationManager.cancel(SELECTED_OPERATOR_FAIL_NOTIFICATION);
   1287     }
   1288 
   1289     /**
   1290      * Update notification about no service of user selected operator
   1291      *
   1292      * @param serviceState Phone service state
   1293      */
   1294     void updateNetworkSelection(int serviceState) {
   1295         if (TelephonyCapabilities.supportsNetworkSelection(mPhone)) {
   1296             // get the shared preference of network_selection.
   1297             // empty is auto mode, otherwise it is the operator alpha name
   1298             // in case there is no operator name, check the operator numeric
   1299             SharedPreferences sp =
   1300                     PreferenceManager.getDefaultSharedPreferences(mContext);
   1301             String networkSelection =
   1302                     sp.getString(PhoneBase.NETWORK_SELECTION_NAME_KEY, "");
   1303             if (TextUtils.isEmpty(networkSelection)) {
   1304                 networkSelection =
   1305                         sp.getString(PhoneBase.NETWORK_SELECTION_KEY, "");
   1306             }
   1307 
   1308             if (DBG) log("updateNetworkSelection()..." + "state = " +
   1309                     serviceState + " new network " + networkSelection);
   1310 
   1311             if (serviceState == ServiceState.STATE_OUT_OF_SERVICE
   1312                     && !TextUtils.isEmpty(networkSelection)) {
   1313                 if (!mSelectedUnavailableNotify) {
   1314                     showNetworkSelection(networkSelection);
   1315                     mSelectedUnavailableNotify = true;
   1316                 }
   1317             } else {
   1318                 if (mSelectedUnavailableNotify) {
   1319                     cancelNetworkSelection();
   1320                     mSelectedUnavailableNotify = false;
   1321                 }
   1322             }
   1323         }
   1324     }
   1325 
   1326     /* package */ void postTransientNotification(int notifyId, CharSequence msg) {
   1327         if (mToast != null) {
   1328             mToast.cancel();
   1329         }
   1330 
   1331         mToast = Toast.makeText(mContext, msg, Toast.LENGTH_LONG);
   1332         mToast.show();
   1333     }
   1334 
   1335     private void log(String msg) {
   1336         Log.d(LOG_TAG, msg);
   1337     }
   1338 }
   1339