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