NotificationMgrpublic class NotificationMgr extends Object implements CallerInfoAsyncQuery.OnQueryCompleteListenerNotificationManager-related utility code for the Phone app. |
Fields Summary |
---|
private static final String | LOG_TAG | private static final boolean | DBG | private static final String[] | CALL_LOG_PROJECTION | static final int | MISSED_CALL_NOTIFICATION | static final int | IN_CALL_NOTIFICATION | static final int | MMI_NOTIFICATION | static final int | NETWORK_SELECTION_NOTIFICATION | static final int | VOICEMAIL_NOTIFICATION | static final int | CALL_FORWARD_NOTIFICATION | static final int | DATA_DISCONNECTED_ROAMING_NOTIFICATION | private static NotificationMgr | sMe | private com.android.internal.telephony.Phone | mPhone | private android.content.Context | mContext | private android.app.NotificationManager | mNotificationMgr | private android.app.StatusBarManager | mStatusBar | private StatusBarMgr | mStatusBarMgr | private android.widget.Toast | mToast | private android.os.IBinder | mSpeakerphoneIcon | private android.os.IBinder | mMuteIcon | private int | mNumberMissedCalls | private int | mInCallResId | private static final int | MAX_VM_NUMBER_RETRIES | private static final int | VM_NUMBER_RETRY_DELAY_MILLIS | private int | mVmNumberRetriesRemaining | private QueryHandler | mQueryHandler | private static final int | CALL_LOG_TOKEN | private static final int | CONTACT_TOKEN | static final String[] | PHONES_PROJECTIONThe projection to use when querying the phones table |
Constructors Summary |
---|
NotificationMgr(android.content.Context context)
mContext = context;
mNotificationMgr = (NotificationManager)
context.getSystemService(Context.NOTIFICATION_SERVICE);
mStatusBar = (StatusBarManager) context.getSystemService(Context.STATUS_BAR_SERVICE);
PhoneApp app = PhoneApp.getInstance();
mPhone = app.phone;
|
Methods Summary |
---|
void | cancelCallInProgressNotification()
if (DBG) log("cancelCallInProgressNotification()...");
if (mInCallResId == 0) {
return;
}
if (DBG) log("cancelCallInProgressNotification: " + mInCallResId);
cancelInCall();
| private void | cancelInCall()
if (DBG) log("cancelInCall()...");
cancelMute();
cancelSpeakerphone();
mNotificationMgr.cancel(IN_CALL_NOTIFICATION);
mInCallResId = 0;
| void | cancelMissedCallNotification()
// reset the number of missed calls to 0.
mNumberMissedCalls = 0;
mNotificationMgr.cancel(MISSED_CALL_NOTIFICATION);
| void | cancelMute()
if (mMuteIcon != null) {
mStatusBar.removeIcon(mMuteIcon);
mMuteIcon = null;
}
| void | cancelSpeakerphone()
if (mSpeakerphoneIcon != null) {
mStatusBar.removeIcon(mSpeakerphoneIcon);
mSpeakerphoneIcon = null;
}
| static com.android.phone.NotificationMgr | getDefault()
return sMe;
| com.android.phone.NotificationMgr$StatusBarMgr | getStatusBarMgr()Factory method
if (mStatusBarMgr == null) {
mStatusBarMgr = new StatusBarMgr();
}
return mStatusBarMgr;
| void | hideDataDisconnectedRoaming()Turns off the "data disconnected due to roaming" notification.
if (DBG) log("hideDataDisconnectedRoaming()...");
mNotificationMgr.cancel(DATA_DISCONNECTED_ROAMING_NOTIFICATION);
| static void | init(android.content.Context context)
sMe = new NotificationMgr(context);
// update the notifications that need to be touched at startup.
sMe.updateNotifications();
| private void | log(java.lang.String msg)
Log.d(LOG_TAG, "[NotificationMgr] " + msg);
| void | notifyMissedCall(java.lang.String name, java.lang.String number, java.lang.String label, long date)Displays a notification about a missed call.
// title resource id
int titleResId;
// the text in the notification's line 1 and 2.
String expandedText, callName;
// increment number of missed calls.
mNumberMissedCalls++;
// get the name for the ticker text
// i.e. "Missed call from <caller name or number>"
if (name != null && TextUtils.isGraphic(name)) {
callName = name;
} else if (!TextUtils.isEmpty(number)){
callName = number;
} else {
// use "unknown" if the caller is unidentifiable.
callName = mContext.getString(R.string.unknown);
}
// display the first line of the notification:
// 1 missed call: call name
// more than 1 missed call: <number of calls> + "missed calls"
if (mNumberMissedCalls == 1) {
titleResId = R.string.notification_missedCallTitle;
expandedText = callName;
} else {
titleResId = R.string.notification_missedCallsTitle;
expandedText = mContext.getString(R.string.notification_missedCallsMsg,
mNumberMissedCalls);
}
// create the target call log intent
final Intent intent = PhoneApp.createCallLogIntent();
// make the notification
mNotificationMgr.notify(
MISSED_CALL_NOTIFICATION,
new Notification(
mContext, // context
android.R.drawable.stat_notify_missed_call, // icon
mContext.getString(
R.string.notification_missedCallTicker, callName), // tickerText
date, // when
mContext.getText(titleResId), // expandedTitle
expandedText, // expandedText
intent // contentIntent
));
| void | notifyMute()
if (mMuteIcon == null) {
mMuteIcon = mStatusBar.addIcon("mute", android.R.drawable.stat_notify_call_mute, 0);
}
| void | notifySpeakerphone()
if (mSpeakerphoneIcon == null) {
mSpeakerphoneIcon = mStatusBar.addIcon("speakerphone",
android.R.drawable.stat_sys_speakerphone, 0);
}
| public void | onQueryComplete(int token, java.lang.Object cookie, com.android.internal.telephony.CallerInfo ci)Implemented for CallerInfoAsyncQuery.OnQueryCompleteListener interface.
refreshes the contentView when called.
if (DBG) log("callerinfo query complete, updating ui.");
((RemoteViews) cookie).setTextViewText(R.id.text2,
PhoneUtils.getCompactNameFromCallerInfo(ci, mContext));
| void | postTransientNotification(int notifyId, java.lang.CharSequence msg)
if (mToast != null) {
mToast.cancel();
}
mToast = Toast.makeText(mContext, msg, Toast.LENGTH_LONG);
mToast.show();
| void | showDataDisconnectedRoaming()Shows the "data disconnected due to roaming" notification, which
appears when you lose data connectivity because you're roaming and
you have the "data roaming" feature turned off.
if (DBG) log("showDataDisconnectedRoaming()...");
Intent intent = new Intent(mContext,
Settings.class); // "Mobile network settings" screen
Notification notification = new Notification(
mContext, // context
android.R.drawable.stat_sys_warning, // icon
null, // tickerText
System.currentTimeMillis(),
mContext.getString(R.string.roaming), // expandedTitle
mContext.getString(R.string.roaming_reenable_message), // expandedText
intent // contentIntent
);
mNotificationMgr.notify(
DATA_DISCONNECTED_ROAMING_NOTIFICATION,
notification);
| void | updateCfi(boolean visible)Updates the message call forwarding indicator notification.
if (DBG) log("updateCfi(): " + visible);
if (visible) {
// If Unconditional Call Forwarding (forward all calls) for VOICE
// is enabled, just show a notification. We'll default to expanded
// view for now, so the there is less confusion about the icon. If
// it is deemed too weird to have CF indications as expanded views,
// then we'll flip the flag back.
// TODO: We may want to take a look to see if the notification can
// display the target to forward calls to. This will require some
// effort though, since there are multiple layers of messages that
// will need to propagate that information.
Notification notification;
final boolean showExpandedNotification = true;
if (showExpandedNotification) {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClassName("com.android.phone",
"com.android.phone.CallFeaturesSetting");
notification = new Notification(
mContext, // context
android.R.drawable.stat_sys_phone_call_forward, // icon
null, // tickerText
0, // The "timestamp" of this notification is meaningless;
// we only care about whether CFI is currently on or not.
mContext.getString(R.string.labelCF), // expandedTitle
mContext.getString(R.string.sum_cfu_enabled_indicator), // expandedText
intent // contentIntent
);
} else {
notification = new Notification(
android.R.drawable.stat_sys_phone_call_forward, // icon
null, // tickerText
System.currentTimeMillis() // when
);
}
notification.flags |= Notification.FLAG_ONGOING_EVENT; // also implies FLAG_NO_CLEAR
mNotificationMgr.notify(
CALL_FORWARD_NOTIFICATION,
notification);
} else {
mNotificationMgr.cancel(CALL_FORWARD_NOTIFICATION);
}
| void | updateInCallNotification()
if (DBG) log("updateInCallNotification()...");
if (mPhone.getState() != Phone.State.OFFHOOK) {
return;
}
final boolean hasActiveCall = !mPhone.getForegroundCall().isIdle();
final boolean hasHoldingCall = !mPhone.getBackgroundCall().isIdle();
// Display the appropriate "in-call" icon in the status bar,
// which depends on the current phone and/or bluetooth state.
int resId = android.R.drawable.stat_sys_phone_call;
if (!hasActiveCall && hasHoldingCall) {
// There's only one call, and it's on hold.
resId = android.R.drawable.stat_sys_phone_call_on_hold;
} else if (PhoneApp.getInstance().showBluetoothIndication()) {
// Bluetooth is active.
resId = com.android.internal.R.drawable.stat_sys_phone_call_bluetooth;
}
// Note we can't just bail out now if (resId == mInCallResId),
// since even if the status icon hasn't changed, some *other*
// notification-related info may be different from the last time
// we were here (like the caller-id info of the foreground call,
// if the user swapped calls...)
if (DBG) log("- Updating status bar icon: " + resId);
mInCallResId = resId;
// Even if both lines are in use, we only show a single item in
// the expanded Notifications UI. It's labeled "Ongoing call"
// (or "On hold" if there's only one call, and it's on hold.)
// The icon in the expanded view is the same as in the status bar.
int expandedViewIcon = mInCallResId;
// Also, we don't have room to display caller-id info from two
// different calls. So if there's only one call, use that, but if
// both lines are in use we display the caller-id info from the
// foreground call and totally ignore the background call.
Call currentCall = hasActiveCall ? mPhone.getForegroundCall()
: mPhone.getBackgroundCall();
Connection currentConn = currentCall.getEarliestConnection();
// When expanded, the "Ongoing call" notification is (visually)
// different from most other Notifications, so we need to use a
// custom view hierarchy.
Notification notification = new Notification();
notification.icon = mInCallResId;
notification.contentIntent = PendingIntent.getActivity(mContext, 0,
PhoneApp.createInCallIntent(), 0);
notification.flags |= Notification.FLAG_ONGOING_EVENT;
// Our custom view, which includes an icon (either "ongoing call" or
// "on hold") and 2 lines of text: (1) the label (either "ongoing
// call" with time counter, or "on hold), and (2) the compact name of
// the current Connection.
RemoteViews contentView = new RemoteViews(mContext.getPackageName(),
R.layout.ongoing_call_notification);
contentView.setImageViewResource(R.id.icon, expandedViewIcon);
// if the connection is valid, then build what we need for the
// first line of notification information, and start the chronometer.
// Otherwise, don't bother and just stick with line 2.
if (currentConn != null) {
// Determine the "start time" of the current connection, in terms
// of the SystemClock.elapsedRealtime() timebase (which is what
// the Chronometer widget needs.)
// We can't use currentConn.getConnectTime(), because (1) that's
// in the currentTimeMillis() time base, and (2) it's zero when
// the phone first goes off hook, since the getConnectTime counter
// doesn't start until the DIALING -> ACTIVE transition.
// Instead we start with the current connection's duration,
// and translate that into the elapsedRealtime() timebase.
long callDurationMsec = currentConn.getDurationMillis();
long chronometerBaseTime = SystemClock.elapsedRealtime() - callDurationMsec;
// Line 1 of the expanded view (in bold text):
String expandedViewLine1;
if (hasHoldingCall && !hasActiveCall) {
// Only one call, and it's on hold!
// Note this isn't a format string! (We want "On hold" here,
// not "On hold (1:23)".) That's OK; if you call
// String.format() with more arguments than format specifiers,
// the extra arguments are ignored.
expandedViewLine1 = mContext.getString(R.string.notification_on_hold);
} else {
// Format string with a "%s" where the current call time should go.
expandedViewLine1 = mContext.getString(R.string.notification_ongoing_call_format);
}
if (DBG) log("- Updating expanded view: line 1 '" + expandedViewLine1 + "'");
// Text line #1 is actually a Chronometer, not a plain TextView.
// We format the elapsed time of the current call into a line like
// "Ongoing call (01:23)".
contentView.setChronometer(R.id.text1,
chronometerBaseTime,
expandedViewLine1,
true);
} else if (DBG) {
log("updateInCallNotification: connection is null, call status not updated.");
}
// display conference call string if this call is a conference
// call, otherwise display the connection information.
// TODO: it may not make sense for every point to make separate
// checks for isConferenceCall, so we need to think about
// possibly including this in startGetCallerInfo or some other
// common point.
String expandedViewLine2 = "";
if (PhoneUtils.isConferenceCall(currentCall)) {
// if this is a conference call, just use that as the caller name.
expandedViewLine2 = mContext.getString(R.string.card_title_conf_call);
} else {
// Start asynchronous call to get the compact name.
PhoneUtils.CallerInfoToken cit =
PhoneUtils.startGetCallerInfo (mContext, currentCall, this, contentView);
// Line 2 of the expanded view (smaller text):
expandedViewLine2 = PhoneUtils.getCompactNameFromCallerInfo(cit.currentInfo, mContext);
}
if (DBG) log("- Updating expanded view: line 2 '" + expandedViewLine2 + "'");
contentView.setTextViewText(R.id.text2, expandedViewLine2);
notification.contentView = contentView;
// TODO: We also need to *update* this notification in some cases,
// like when a call ends on one line but the other is still in use
// (ie. make sure the caller info here corresponds to the active
// line), and maybe even when the user swaps calls (ie. if we only
// show info here for the "current active call".)
if (DBG) log("Notifying IN_CALL_NOTIFICATION: " + notification);
mNotificationMgr.notify(IN_CALL_NOTIFICATION,
notification);
// Finally, refresh the mute and speakerphone notifications (since
// some phone state changes can indirectly affect the mute and/or
// speaker state).
updateSpeakerNotification();
updateMuteNotification();
| void | updateMuteNotification()Calls either notifyMute() or cancelMute() based on
the actual current mute state of the Phone.
if ((mPhone.getState() == Phone.State.OFFHOOK) && mPhone.getMute()) {
if (DBG) log("updateMuteNotification: MUTED");
notifyMute();
} else {
if (DBG) log("updateMuteNotification: not muted (or not offhook)");
cancelMute();
}
| void | updateMwi(boolean visible)Updates the message waiting indicator (voicemail) notification.
if (DBG) log("updateMwi(): " + visible);
if (visible) {
int resId = android.R.drawable.stat_notify_voicemail;
// This Notification can get a lot fancier once we have more
// information about the current voicemail messages.
// (For example, the current voicemail system can't tell
// us the caller-id or timestamp of a message, or tell us the
// message count.)
// But for now, the UI is ultra-simple: if the MWI indication
// is supposed to be visible, just show a single generic
// notification.
String notificationTitle = mContext.getString(R.string.notification_voicemail_title);
String vmNumber = mPhone.getVoiceMailNumber();
if (DBG) log("- got vm number: '" + vmNumber + "'");
// Watch out: vmNumber may be null, for two possible reasons:
//
// (1) This phone really has no voicemail number
//
// (2) This phone *does* have a voicemail number, but
// the SIM isn't ready yet.
//
// Case (2) *does* happen in practice if you have voicemail
// messages when the device first boots: we get an MWI
// notification as soon as we register on the network, but the
// SIM hasn't finished loading yet.
//
// So handle case (2) by retrying the lookup after a short
// delay.
if ((vmNumber == null) && !mPhone.getSimRecordsLoaded()) {
if (DBG) log("- Null vm number: SIM records not loaded (yet)...");
// TODO: rather than retrying after an arbitrary delay, it
// would be cleaner to instead just wait for a
// SIM_RECORDS_LOADED notification.
// (Unfortunately right now there's no convenient way to
// get that notification in phone app code. We'd first
// want to add a call like registerForSimRecordsLoaded()
// to Phone.java and GSMPhone.java, and *then* we could
// listen for that in the CallNotifier class.)
// Limit the number of retries (in case the SIM is broken
// or missing and can *never* load successfully.)
if (mVmNumberRetriesRemaining-- > 0) {
if (DBG) log(" - Retrying in " + VM_NUMBER_RETRY_DELAY_MILLIS + " msec...");
PhoneApp.getInstance().notifier.sendMwiChangedDelayed(
VM_NUMBER_RETRY_DELAY_MILLIS);
return;
} else {
Log.w(LOG_TAG, "NotificationMgr.updateMwi: getVoiceMailNumber() failed after "
+ MAX_VM_NUMBER_RETRIES + " retries; giving up.");
// ...and continue with vmNumber==null, just as if the
// phone had no VM number set up in the first place.
}
}
String notificationText;
if (TextUtils.isEmpty(vmNumber)) {
notificationText = mContext.getString(
R.string.notification_voicemail_no_vm_number);
} else {
notificationText = String.format(
mContext.getString(R.string.notification_voicemail_text_format),
PhoneNumberUtils.formatNumber(vmNumber));
}
Intent intent = new Intent(Intent.ACTION_CALL,
Uri.fromParts("voicemail", "", null));
PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
Notification notification = new Notification(
resId, // icon
null, // tickerText
System.currentTimeMillis() // Show the time the MWI notification came in,
// since we don't know the actual time of the
// most recent voicemail message
);
notification.setLatestEventInfo(
mContext, // context
notificationTitle, // contentTitle
notificationText, // contentText
pendingIntent // contentIntent
);
notification.defaults |= Notification.DEFAULT_SOUND;
notification.flags |= Notification.FLAG_NO_CLEAR;
notification.flags |= Notification.FLAG_SHOW_LIGHTS;
notification.ledARGB = 0xff00ff00;
notification.ledOnMS = 500;
notification.ledOffMS = 2000;
mNotificationMgr.notify(
VOICEMAIL_NOTIFICATION,
notification);
} else {
mNotificationMgr.cancel(VOICEMAIL_NOTIFICATION);
}
| void | updateNotifications()Makes sure notifications are up to date.
if (DBG) log("begin querying call log");
// instantiate query handler
mQueryHandler = new QueryHandler(mContext.getContentResolver());
// setup query spec, look for all Missed calls that are new.
StringBuilder where = new StringBuilder("type=");
where.append(Calls.MISSED_TYPE);
where.append(" AND new=1");
// start the query
mQueryHandler.startQuery(CALL_LOG_TOKEN, null, Calls.CONTENT_URI, CALL_LOG_PROJECTION,
where.toString(), null, Calls.DEFAULT_SORT_ORDER);
// synchronize the in call notification
if (mPhone.getState() != Phone.State.OFFHOOK) {
if (DBG) log("Phone is idle, canceling notification.");
cancelInCall();
} else {
if (DBG) log("Phone is offhook, updating notification.");
updateInCallNotification();
}
// Depend on android.app.StatusBarManager to be set to
// disable(DISABLE_NONE) upon startup. This will be the
// case even if the phone app crashes.
| void | updateSpeakerNotification()Calls either notifySpeakerphone() or cancelSpeakerphone() based on
the actual current state of the speaker.
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
if ((mPhone.getState() == Phone.State.OFFHOOK) && audioManager.isSpeakerphoneOn()) {
if (DBG) log("updateSpeakerNotification: speaker ON");
notifySpeakerphone();
} else {
if (DBG) log("updateSpeakerNotification: speaker OFF (or not offhook)");
cancelSpeakerphone();
}
|
|