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