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