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