1 /* 2 * Copyright (C) 2008 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.internal.location; 18 19 import android.app.Notification; 20 import android.app.NotificationManager; 21 import android.app.PendingIntent; 22 import android.compat.annotation.UnsupportedAppUsage; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.location.INetInitiatedListener; 28 import android.location.LocationManager; 29 import android.os.RemoteException; 30 import android.os.SystemClock; 31 import android.os.UserHandle; 32 import android.telephony.PhoneNumberUtils; 33 import android.telephony.PhoneStateListener; 34 import android.telephony.TelephonyManager; 35 import android.util.Log; 36 37 import com.android.internal.R; 38 import com.android.internal.notification.SystemNotificationChannels; 39 import com.android.internal.telephony.GsmAlphabet; 40 41 import java.io.UnsupportedEncodingException; 42 import java.util.concurrent.TimeUnit; 43 44 /** 45 * A GPS Network-initiated Handler class used by LocationManager. 46 * 47 * {@hide} 48 */ 49 public class GpsNetInitiatedHandler { 50 51 private static final String TAG = "GpsNetInitiatedHandler"; 52 53 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 54 55 // NI verify activity for bringing up UI (not used yet) 56 public static final String ACTION_NI_VERIFY = "android.intent.action.NETWORK_INITIATED_VERIFY"; 57 58 // string constants for defining data fields in NI Intent 59 public static final String NI_INTENT_KEY_NOTIF_ID = "notif_id"; 60 public static final String NI_INTENT_KEY_TITLE = "title"; 61 public static final String NI_INTENT_KEY_MESSAGE = "message"; 62 public static final String NI_INTENT_KEY_TIMEOUT = "timeout"; 63 public static final String NI_INTENT_KEY_DEFAULT_RESPONSE = "default_resp"; 64 65 // the extra command to send NI response to GnssLocationProvider 66 public static final String NI_RESPONSE_EXTRA_CMD = "send_ni_response"; 67 68 // the extra command parameter names in the Bundle 69 public static final String NI_EXTRA_CMD_NOTIF_ID = "notif_id"; 70 public static final String NI_EXTRA_CMD_RESPONSE = "response"; 71 72 // these need to match GpsNiType constants in gps_ni.h 73 public static final int GPS_NI_TYPE_VOICE = 1; 74 public static final int GPS_NI_TYPE_UMTS_SUPL = 2; 75 public static final int GPS_NI_TYPE_UMTS_CTRL_PLANE = 3; 76 public static final int GPS_NI_TYPE_EMERGENCY_SUPL = 4; 77 78 // these need to match GpsUserResponseType constants in gps_ni.h 79 public static final int GPS_NI_RESPONSE_ACCEPT = 1; 80 public static final int GPS_NI_RESPONSE_DENY = 2; 81 public static final int GPS_NI_RESPONSE_NORESP = 3; 82 public static final int GPS_NI_RESPONSE_IGNORE = 4; 83 84 // these need to match GpsNiNotifyFlags constants in gps_ni.h 85 public static final int GPS_NI_NEED_NOTIFY = 0x0001; 86 public static final int GPS_NI_NEED_VERIFY = 0x0002; 87 public static final int GPS_NI_PRIVACY_OVERRIDE = 0x0004; 88 89 // these need to match GpsNiEncodingType in gps_ni.h 90 public static final int GPS_ENC_NONE = 0; 91 public static final int GPS_ENC_SUPL_GSM_DEFAULT = 1; 92 public static final int GPS_ENC_SUPL_UTF8 = 2; 93 public static final int GPS_ENC_SUPL_UCS2 = 3; 94 public static final int GPS_ENC_UNKNOWN = -1; 95 96 private final Context mContext; 97 private final TelephonyManager mTelephonyManager; 98 private final PhoneStateListener mPhoneStateListener; 99 100 // parent gps location provider 101 private final LocationManager mLocationManager; 102 103 // configuration of notificaiton behavior 104 private boolean mPlaySounds = false; 105 private boolean mPopupImmediately = true; 106 107 // read the SUPL_ES form gps.conf 108 private volatile boolean mIsSuplEsEnabled; 109 110 // Set to true if the phone is having emergency call. 111 private volatile boolean mIsInEmergencyCall; 112 113 // If Location function is enabled. 114 private volatile boolean mIsLocationEnabled = false; 115 116 private final INetInitiatedListener mNetInitiatedListener; 117 118 // Set to true if string from HAL is encoded as Hex, e.g., "3F0039" 119 @UnsupportedAppUsage 120 static private boolean mIsHexInput = true; 121 122 // End time of emergency call, and extension, if set 123 private volatile long mCallEndElapsedRealtimeMillis = 0; 124 private volatile long mEmergencyExtensionMillis = 0; 125 126 public static class GpsNiNotification 127 { 128 @android.compat.annotation.UnsupportedAppUsage GpsNiNotification()129 public GpsNiNotification() { 130 } 131 public int notificationId; 132 public int niType; 133 public boolean needNotify; 134 public boolean needVerify; 135 public boolean privacyOverride; 136 public int timeout; 137 public int defaultResponse; 138 @UnsupportedAppUsage 139 public String requestorId; 140 @UnsupportedAppUsage 141 public String text; 142 @UnsupportedAppUsage 143 public int requestorIdEncoding; 144 @UnsupportedAppUsage 145 public int textEncoding; 146 }; 147 148 public static class GpsNiResponse { 149 /* User response, one of the values in GpsUserResponseType */ 150 int userResponse; 151 }; 152 153 private final BroadcastReceiver mBroadcastReciever = new BroadcastReceiver() { 154 155 @Override public void onReceive(Context context, Intent intent) { 156 String action = intent.getAction(); 157 if (action.equals(Intent.ACTION_NEW_OUTGOING_CALL)) { 158 String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); 159 /* 160 Tracks the emergency call: 161 mIsInEmergencyCall records if the phone is in emergency call or not. It will 162 be set to true when the phone is having emergency call, and then will 163 be set to false by mPhoneStateListener when the emergency call ends. 164 */ 165 mIsInEmergencyCall = PhoneNumberUtils.isEmergencyNumber(phoneNumber); 166 if (DEBUG) Log.v(TAG, "ACTION_NEW_OUTGOING_CALL - " + getInEmergency()); 167 } else if (action.equals(LocationManager.MODE_CHANGED_ACTION)) { 168 updateLocationMode(); 169 if (DEBUG) Log.d(TAG, "location enabled :" + getLocationEnabled()); 170 } 171 } 172 }; 173 174 /** 175 * The notification that is shown when a network-initiated notification 176 * (and verification) event is received. 177 * <p> 178 * This is lazily created, so use {@link #setNINotification()}. 179 */ 180 private Notification.Builder mNiNotificationBuilder; 181 GpsNetInitiatedHandler(Context context, INetInitiatedListener netInitiatedListener, boolean isSuplEsEnabled)182 public GpsNetInitiatedHandler(Context context, 183 INetInitiatedListener netInitiatedListener, 184 boolean isSuplEsEnabled) { 185 mContext = context; 186 187 if (netInitiatedListener == null) { 188 throw new IllegalArgumentException("netInitiatedListener is null"); 189 } else { 190 mNetInitiatedListener = netInitiatedListener; 191 } 192 193 setSuplEsEnabled(isSuplEsEnabled); 194 mLocationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE); 195 updateLocationMode(); 196 mTelephonyManager = 197 (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); 198 199 mPhoneStateListener = new PhoneStateListener() { 200 @Override 201 public void onCallStateChanged(int state, String incomingNumber) { 202 if (DEBUG) Log.d(TAG, "onCallStateChanged(): state is "+ state); 203 // listening for emergency call ends 204 if (state == TelephonyManager.CALL_STATE_IDLE) { 205 if (mIsInEmergencyCall) { 206 mCallEndElapsedRealtimeMillis = SystemClock.elapsedRealtime(); 207 mIsInEmergencyCall = false; 208 } 209 } 210 } 211 }; 212 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); 213 214 IntentFilter intentFilter = new IntentFilter(); 215 intentFilter.addAction(Intent.ACTION_NEW_OUTGOING_CALL); 216 intentFilter.addAction(LocationManager.MODE_CHANGED_ACTION); 217 mContext.registerReceiver(mBroadcastReciever, intentFilter); 218 } 219 setSuplEsEnabled(boolean isEnabled)220 public void setSuplEsEnabled(boolean isEnabled) { 221 mIsSuplEsEnabled = isEnabled; 222 } 223 getSuplEsEnabled()224 public boolean getSuplEsEnabled() { 225 return mIsSuplEsEnabled; 226 } 227 228 /** 229 * Updates Location enabler based on location setting. 230 */ updateLocationMode()231 public void updateLocationMode() { 232 mIsLocationEnabled = mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); 233 } 234 235 /** 236 * Checks if user agreed to use location. 237 */ getLocationEnabled()238 public boolean getLocationEnabled() { 239 return mIsLocationEnabled; 240 } 241 242 /** 243 * Determines whether device is in user-initiated emergency session based on the following 244 * 1. If the user is making an emergency call, this is provided by actively 245 * monitoring the outgoing phone number; 246 * 2. If the user has recently ended an emergency call, and the device is in a configured time 247 * window after the end of that call. 248 * 3. If the device is in a emergency callback state, this is provided by querying 249 * TelephonyManager. 250 * 4. If the user has recently sent an Emergency SMS and telephony reports that it is in 251 * emergency SMS mode, this is provided by querying TelephonyManager. 252 * @return true if is considered in user initiated emergency mode for NI purposes 253 */ getInEmergency()254 public boolean getInEmergency() { 255 boolean isInEmergencyExtension = 256 (mCallEndElapsedRealtimeMillis > 0) 257 && ((SystemClock.elapsedRealtime() - mCallEndElapsedRealtimeMillis) 258 < mEmergencyExtensionMillis); 259 boolean isInEmergencyCallback = mTelephonyManager.getEmergencyCallbackMode(); 260 boolean isInEmergencySmsMode = mTelephonyManager.isInEmergencySmsMode(); 261 return mIsInEmergencyCall || isInEmergencyCallback || isInEmergencyExtension 262 || isInEmergencySmsMode; 263 } 264 setEmergencyExtensionSeconds(int emergencyExtensionSeconds)265 public void setEmergencyExtensionSeconds(int emergencyExtensionSeconds) { 266 mEmergencyExtensionMillis = TimeUnit.SECONDS.toMillis(emergencyExtensionSeconds); 267 } 268 269 // Handles NI events from HAL 270 @UnsupportedAppUsage handleNiNotification(GpsNiNotification notif)271 public void handleNiNotification(GpsNiNotification notif) { 272 if (DEBUG) Log.d(TAG, "in handleNiNotification () :" 273 + " notificationId: " + notif.notificationId 274 + " requestorId: " + notif.requestorId 275 + " text: " + notif.text 276 + " mIsSuplEsEnabled" + getSuplEsEnabled() 277 + " mIsLocationEnabled" + getLocationEnabled()); 278 279 if (getSuplEsEnabled()) { 280 handleNiInEs(notif); 281 } else { 282 handleNi(notif); 283 } 284 285 ////////////////////////////////////////////////////////////////////////// 286 // A note about timeout 287 // According to the protocol, in the need_notify and need_verify case, 288 // a default response should be sent when time out. 289 // 290 // In some GPS hardware, the GPS driver (under HAL) can handle the timeout case 291 // and this class GpsNetInitiatedHandler does not need to do anything. 292 // 293 // However, the UI should at least close the dialog when timeout. Further, 294 // for more general handling, timeout response should be added to the Handler here. 295 // 296 } 297 298 // handle NI form HAL when SUPL_ES is disabled. handleNi(GpsNiNotification notif)299 private void handleNi(GpsNiNotification notif) { 300 if (DEBUG) Log.d(TAG, "in handleNi () :" 301 + " needNotify: " + notif.needNotify 302 + " needVerify: " + notif.needVerify 303 + " privacyOverride: " + notif.privacyOverride 304 + " mPopupImmediately: " + mPopupImmediately 305 + " mInEmergency: " + getInEmergency()); 306 307 if (!getLocationEnabled() && !getInEmergency()) { 308 // Location is currently disabled, ignore all NI requests. 309 try { 310 mNetInitiatedListener.sendNiResponse(notif.notificationId, 311 GPS_NI_RESPONSE_IGNORE); 312 } catch (RemoteException e) { 313 Log.e(TAG, "RemoteException in sendNiResponse"); 314 } 315 } 316 if (notif.needNotify) { 317 // If NI does not need verify or the dialog is not requested 318 // to pop up immediately, the dialog box will not pop up. 319 if (notif.needVerify && mPopupImmediately) { 320 // Popup the dialog box now 321 openNiDialog(notif); 322 } else { 323 // Show the notification 324 setNiNotification(notif); 325 } 326 } 327 // ACCEPT cases: 1. Notify, no verify; 2. no notify, no verify; 328 // 3. privacy override. 329 if (!notif.needVerify || notif.privacyOverride) { 330 try { 331 mNetInitiatedListener.sendNiResponse(notif.notificationId, 332 GPS_NI_RESPONSE_ACCEPT); 333 } catch (RemoteException e) { 334 Log.e(TAG, "RemoteException in sendNiResponse"); 335 } 336 } 337 } 338 339 // handle NI from HAL when the SUPL_ES is enabled handleNiInEs(GpsNiNotification notif)340 private void handleNiInEs(GpsNiNotification notif) { 341 342 if (DEBUG) Log.d(TAG, "in handleNiInEs () :" 343 + " niType: " + notif.niType 344 + " notificationId: " + notif.notificationId); 345 346 // UE is in emergency mode when in emergency call mode or in emergency call back mode 347 /* 348 1. When SUPL ES bit is off and UE is not in emergency mode: 349 Call handleNi() to do legacy behaviour. 350 2. When SUPL ES bit is on and UE is in emergency mode: 351 Call handleNi() to do acceptance behaviour. 352 3. When SUPL ES bit is off but UE is in emergency mode: 353 Ignore the emergency SUPL INIT. 354 4. When SUPL ES bit is on but UE is not in emergency mode: 355 Ignore the emergency SUPL INIT. 356 */ 357 boolean isNiTypeES = (notif.niType == GPS_NI_TYPE_EMERGENCY_SUPL); 358 if (isNiTypeES != getInEmergency()) { 359 try { 360 mNetInitiatedListener.sendNiResponse(notif.notificationId, 361 GPS_NI_RESPONSE_IGNORE); 362 } catch (RemoteException e) { 363 Log.e(TAG, "RemoteException in sendNiResponse"); 364 } 365 } else { 366 handleNi(notif); 367 } 368 } 369 370 /** 371 * Posts a notification in the status bar using the contents in {@code notif} object. 372 */ setNiNotification(GpsNiNotification notif)373 private synchronized void setNiNotification(GpsNiNotification notif) { 374 NotificationManager notificationManager = (NotificationManager) mContext 375 .getSystemService(Context.NOTIFICATION_SERVICE); 376 if (notificationManager == null) { 377 return; 378 } 379 380 String title = getNotifTitle(notif, mContext); 381 String message = getNotifMessage(notif, mContext); 382 383 if (DEBUG) Log.d(TAG, "setNiNotification, notifyId: " + notif.notificationId + 384 ", title: " + title + 385 ", message: " + message); 386 387 // Construct Notification 388 if (mNiNotificationBuilder == null) { 389 mNiNotificationBuilder = new Notification.Builder(mContext, 390 SystemNotificationChannels.NETWORK_ALERTS) 391 .setSmallIcon(com.android.internal.R.drawable.stat_sys_gps_on) 392 .setWhen(0) 393 .setOngoing(true) 394 .setAutoCancel(true) 395 .setColor(mContext.getColor( 396 com.android.internal.R.color.system_notification_accent_color)); 397 } 398 399 if (mPlaySounds) { 400 mNiNotificationBuilder.setDefaults(Notification.DEFAULT_SOUND); 401 } else { 402 mNiNotificationBuilder.setDefaults(0); 403 } 404 405 // if not to popup dialog immediately, pending intent will open the dialog 406 Intent intent = !mPopupImmediately ? getDlgIntent(notif) : new Intent(); 407 PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 0); 408 mNiNotificationBuilder.setTicker(getNotifTicker(notif, mContext)) 409 .setContentTitle(title) 410 .setContentText(message) 411 .setContentIntent(pi); 412 413 notificationManager.notifyAsUser(null, notif.notificationId, mNiNotificationBuilder.build(), 414 UserHandle.ALL); 415 } 416 417 // Opens the notification dialog and waits for user input openNiDialog(GpsNiNotification notif)418 private void openNiDialog(GpsNiNotification notif) 419 { 420 Intent intent = getDlgIntent(notif); 421 422 if (DEBUG) Log.d(TAG, "openNiDialog, notifyId: " + notif.notificationId + 423 ", requestorId: " + notif.requestorId + 424 ", text: " + notif.text); 425 426 mContext.startActivity(intent); 427 } 428 429 // Construct the intent for bringing up the dialog activity, which shows the 430 // notification and takes user input getDlgIntent(GpsNiNotification notif)431 private Intent getDlgIntent(GpsNiNotification notif) 432 { 433 Intent intent = new Intent(); 434 String title = getDialogTitle(notif, mContext); 435 String message = getDialogMessage(notif, mContext); 436 437 // directly bring up the NI activity 438 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); 439 intent.setClass(mContext, com.android.internal.app.NetInitiatedActivity.class); 440 441 // put data in the intent 442 intent.putExtra(NI_INTENT_KEY_NOTIF_ID, notif.notificationId); 443 intent.putExtra(NI_INTENT_KEY_TITLE, title); 444 intent.putExtra(NI_INTENT_KEY_MESSAGE, message); 445 intent.putExtra(NI_INTENT_KEY_TIMEOUT, notif.timeout); 446 intent.putExtra(NI_INTENT_KEY_DEFAULT_RESPONSE, notif.defaultResponse); 447 448 if (DEBUG) Log.d(TAG, "generateIntent, title: " + title + ", message: " + message + 449 ", timeout: " + notif.timeout); 450 451 return intent; 452 } 453 454 // Converts a string (or Hex string) to a char array stringToByteArray(String original, boolean isHex)455 static byte[] stringToByteArray(String original, boolean isHex) 456 { 457 int length = isHex ? original.length() / 2 : original.length(); 458 byte[] output = new byte[length]; 459 int i; 460 461 if (isHex) 462 { 463 for (i = 0; i < length; i++) 464 { 465 output[i] = (byte) Integer.parseInt(original.substring(i*2, i*2+2), 16); 466 } 467 } 468 else { 469 for (i = 0; i < length; i++) 470 { 471 output[i] = (byte) original.charAt(i); 472 } 473 } 474 475 return output; 476 } 477 478 /** 479 * Unpacks an byte array containing 7-bit packed characters into a String. 480 * 481 * @param input a 7-bit packed char array 482 * @return the unpacked String 483 */ decodeGSMPackedString(byte[] input)484 static String decodeGSMPackedString(byte[] input) 485 { 486 final char PADDING_CHAR = 0x00; 487 int lengthBytes = input.length; 488 int lengthSeptets = (lengthBytes * 8) / 7; 489 String decoded; 490 491 /* Special case where the last 7 bits in the last byte could hold a valid 492 * 7-bit character or a padding character. Drop the last 7-bit character 493 * if it is a padding character. 494 */ 495 if (lengthBytes % 7 == 0) { 496 if (lengthBytes > 0) { 497 if ((input[lengthBytes - 1] >> 1) == PADDING_CHAR) { 498 lengthSeptets = lengthSeptets - 1; 499 } 500 } 501 } 502 503 decoded = GsmAlphabet.gsm7BitPackedToString(input, 0, lengthSeptets); 504 505 // Return "" if decoding of GSM packed string fails 506 if (null == decoded) { 507 Log.e(TAG, "Decoding of GSM packed string failed"); 508 decoded = ""; 509 } 510 511 return decoded; 512 } 513 decodeUTF8String(byte[] input)514 static String decodeUTF8String(byte[] input) 515 { 516 String decoded = ""; 517 try { 518 decoded = new String(input, "UTF-8"); 519 } 520 catch (UnsupportedEncodingException e) 521 { 522 throw new AssertionError(); 523 } 524 return decoded; 525 } 526 decodeUCS2String(byte[] input)527 static String decodeUCS2String(byte[] input) 528 { 529 String decoded = ""; 530 try { 531 decoded = new String(input, "UTF-16"); 532 } 533 catch (UnsupportedEncodingException e) 534 { 535 throw new AssertionError(); 536 } 537 return decoded; 538 } 539 540 /** Decode NI string 541 * 542 * @param original The text string to be decoded 543 * @param isHex Specifies whether the content of the string has been encoded as a Hex string. Encoding 544 * a string as Hex can allow zeros inside the coded text. 545 * @param coding Specifies the coding scheme of the string, such as GSM, UTF8, UCS2, etc. This coding scheme 546 * needs to match those used passed to HAL from the native GPS driver. Decoding is done according 547 * to the <code> coding </code>, after a Hex string is decoded. Generally, if the 548 * notification strings don't need further decoding, <code> coding </code> encoding can be 549 * set to -1, and <code> isHex </code> can be false. 550 * @return the decoded string 551 */ 552 @UnsupportedAppUsage decodeString(String original, boolean isHex, int coding)553 static private String decodeString(String original, boolean isHex, int coding) 554 { 555 if (coding == GPS_ENC_NONE || coding == GPS_ENC_UNKNOWN) { 556 return original; 557 } 558 559 byte[] input = stringToByteArray(original, isHex); 560 561 switch (coding) { 562 case GPS_ENC_SUPL_GSM_DEFAULT: 563 return decodeGSMPackedString(input); 564 565 case GPS_ENC_SUPL_UTF8: 566 return decodeUTF8String(input); 567 568 case GPS_ENC_SUPL_UCS2: 569 return decodeUCS2String(input); 570 571 default: 572 Log.e(TAG, "Unknown encoding " + coding + " for NI text " + original); 573 return original; 574 } 575 } 576 577 // change this to configure notification display getNotifTicker(GpsNiNotification notif, Context context)578 static private String getNotifTicker(GpsNiNotification notif, Context context) 579 { 580 String ticker = String.format(context.getString(R.string.gpsNotifTicker), 581 decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding), 582 decodeString(notif.text, mIsHexInput, notif.textEncoding)); 583 return ticker; 584 } 585 586 // change this to configure notification display getNotifTitle(GpsNiNotification notif, Context context)587 static private String getNotifTitle(GpsNiNotification notif, Context context) 588 { 589 String title = String.format(context.getString(R.string.gpsNotifTitle)); 590 return title; 591 } 592 593 // change this to configure notification display getNotifMessage(GpsNiNotification notif, Context context)594 static private String getNotifMessage(GpsNiNotification notif, Context context) 595 { 596 String message = String.format(context.getString(R.string.gpsNotifMessage), 597 decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding), 598 decodeString(notif.text, mIsHexInput, notif.textEncoding)); 599 return message; 600 } 601 602 // change this to configure dialog display (for verification) getDialogTitle(GpsNiNotification notif, Context context)603 static public String getDialogTitle(GpsNiNotification notif, Context context) 604 { 605 return getNotifTitle(notif, context); 606 } 607 608 // change this to configure dialog display (for verification) getDialogMessage(GpsNiNotification notif, Context context)609 static private String getDialogMessage(GpsNiNotification notif, Context context) 610 { 611 return getNotifMessage(notif, context); 612 } 613 614 } 615