GpsNetInitiatedHandler.java revision dea74b0285ef946cceb4f56e576800cbedbc3a95
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 17package com.android.internal.location; 18 19import java.io.UnsupportedEncodingException; 20 21import android.app.Notification; 22import android.app.NotificationManager; 23import android.app.PendingIntent; 24import android.content.Context; 25import android.content.Intent; 26import android.os.Bundle; 27import android.os.RemoteException; 28import android.util.Log; 29 30import com.android.internal.R; 31 32/** 33 * A GPS Network-initiated Handler class used by LocationManager. 34 * 35 * {@hide} 36 */ 37public class GpsNetInitiatedHandler { 38 39 private static final String TAG = "GpsNetInitiatedHandler"; 40 41 private static final boolean DEBUG = true; 42 private static final boolean VERBOSE = false; 43 44 // NI verify activity for bringing up UI (not used yet) 45 public static final String ACTION_NI_VERIFY = "android.intent.action.NETWORK_INITIATED_VERIFY"; 46 47 // string constants for defining data fields in NI Intent 48 public static final String NI_INTENT_KEY_NOTIF_ID = "notif_id"; 49 public static final String NI_INTENT_KEY_TITLE = "title"; 50 public static final String NI_INTENT_KEY_MESSAGE = "message"; 51 public static final String NI_INTENT_KEY_TIMEOUT = "timeout"; 52 public static final String NI_INTENT_KEY_DEFAULT_RESPONSE = "default_resp"; 53 54 // the extra command to send NI response to GpsLocationProvider 55 public static final String NI_RESPONSE_EXTRA_CMD = "send_ni_response"; 56 57 // the extra command parameter names in the Bundle 58 public static final String NI_EXTRA_CMD_NOTIF_ID = "notif_id"; 59 public static final String NI_EXTRA_CMD_RESPONSE = "response"; 60 61 // these need to match GpsNiType constants in gps_ni.h 62 public static final int GPS_NI_TYPE_VOICE = 1; 63 public static final int GPS_NI_TYPE_UMTS_SUPL = 2; 64 public static final int GPS_NI_TYPE_UMTS_CTRL_PLANE = 3; 65 66 // these need to match GpsUserResponseType constants in gps_ni.h 67 public static final int GPS_NI_RESPONSE_ACCEPT = 1; 68 public static final int GPS_NI_RESPONSE_DENY = 2; 69 public static final int GPS_NI_RESPONSE_NORESP = 3; 70 71 // these need to match GpsNiNotifyFlags constants in gps_ni.h 72 public static final int GPS_NI_NEED_NOTIFY = 0x0001; 73 public static final int GPS_NI_NEED_VERIFY = 0x0002; 74 public static final int GPS_NI_PRIVACY_OVERRIDE = 0x0004; 75 76 // these need to match GpsNiEncodingType in gps_ni.h 77 public static final int GPS_ENC_NONE = 0; 78 public static final int GPS_ENC_SUPL_GSM_DEFAULT = 1; 79 public static final int GPS_ENC_SUPL_UTF8 = 2; 80 public static final int GPS_ENC_SUPL_UCS2 = 3; 81 public static final int GPS_ENC_UNKNOWN = -1; 82 83 private final Context mContext; 84 85 // parent gps location provider 86 private final GpsLocationProvider mGpsLocationProvider; 87 88 // configuration of notificaiton behavior 89 private boolean mPlaySounds = false; 90 private boolean visible = true; 91 private boolean mPopupImmediately = true; 92 93 // Set to true if string from HAL is encoded as Hex, e.g., "3F0039" 94 static private boolean mIsHexInput = true; 95 96 public static class GpsNiNotification 97 { 98 int notificationId; 99 int niType; 100 boolean needNotify; 101 boolean needVerify; 102 boolean privacyOverride; 103 int timeout; 104 int defaultResponse; 105 String requestorId; 106 String text; 107 int requestorIdEncoding; 108 int textEncoding; 109 Bundle extras; 110 }; 111 112 public static class GpsNiResponse { 113 /* User reponse, one of the values in GpsUserResponseType */ 114 int userResponse; 115 /* Optional extra data to pass with the user response */ 116 Bundle extras; 117 }; 118 119 /** 120 * The notification that is shown when a network-initiated notification 121 * (and verification) event is received. 122 * <p> 123 * This is lazily created, so use {@link #setNINotification()}. 124 */ 125 private Notification mNiNotification; 126 127 public GpsNetInitiatedHandler(Context context, GpsLocationProvider gpsLocationProvider) { 128 mContext = context; 129 mGpsLocationProvider = gpsLocationProvider; 130 } 131 132 // Handles NI events from HAL 133 public void handleNiNotification(GpsNiNotification notif) 134 { 135 if (DEBUG) Log.d(TAG, "handleNiNotification" + " notificationId: " + notif.notificationId 136 + " requestorId: " + notif.requestorId + " text: " + notif.text); 137 138 // Notify and verify with immediate pop-up 139 if (notif.needNotify && notif.needVerify && mPopupImmediately) 140 { 141 // Popup the dialog box now 142 openNiDialog(notif); 143 } 144 145 // Notify only, or delayed pop-up (change mPopupImmediately to FALSE) 146 if (notif.needNotify && !notif.needVerify || 147 notif.needNotify && notif.needVerify && !mPopupImmediately) 148 { 149 // Show the notification 150 151 // if mPopupImmediately == FALSE and needVerify == TRUE, a dialog will be opened 152 // when the user opens the notification message 153 154 setNiNotification(notif); 155 } 156 157 // ACCEPT cases: 1. Notify, no verify; 2. no notify, no verify; 3. privacy override. 158 if ( notif.needNotify && !notif.needVerify || 159 !notif.needNotify && !notif.needVerify || 160 notif.privacyOverride) 161 { 162 try { 163 mGpsLocationProvider.getNetInitiatedListener().sendNiResponse(notif.notificationId, GPS_NI_RESPONSE_ACCEPT); 164 } 165 catch (RemoteException e) 166 { 167 Log.e(TAG, e.getMessage()); 168 } 169 } 170 171 ////////////////////////////////////////////////////////////////////////// 172 // A note about timeout 173 // According to the protocol, in the need_notify and need_verify case, 174 // a default response should be sent when time out. 175 // 176 // In some GPS hardware, the GPS driver (under HAL) can handle the timeout case 177 // and this class GpsNetInitiatedHandler does not need to do anything. 178 // 179 // However, the UI should at least close the dialog when timeout. Further, 180 // for more general handling, timeout response should be added to the Handler here. 181 // 182 } 183 184 // Sets the NI notification. 185 private synchronized void setNiNotification(GpsNiNotification notif) { 186 NotificationManager notificationManager = (NotificationManager) mContext 187 .getSystemService(Context.NOTIFICATION_SERVICE); 188 if (notificationManager == null) { 189 return; 190 } 191 192 String title = getNotifTitle(notif, mContext); 193 String message = getNotifMessage(notif, mContext); 194 195 if (DEBUG) Log.d(TAG, "setNiNotification, notifyId: " + notif.notificationId + 196 ", title: " + title + 197 ", message: " + message); 198 199 // Construct Notification 200 if (mNiNotification == null) { 201 mNiNotification = new Notification(); 202 mNiNotification.icon = com.android.internal.R.drawable.stat_sys_gps_on; /* Change notification icon here */ 203 mNiNotification.when = 0; 204 } 205 206 if (mPlaySounds) { 207 mNiNotification.defaults |= Notification.DEFAULT_SOUND; 208 } else { 209 mNiNotification.defaults &= ~Notification.DEFAULT_SOUND; 210 } 211 212 mNiNotification.flags = Notification.FLAG_ONGOING_EVENT; 213 mNiNotification.tickerText = getNotifTicker(notif, mContext); 214 215 // if not to popup dialog immediately, pending intent will open the dialog 216 Intent intent = !mPopupImmediately ? getDlgIntent(notif) : new Intent(); 217 PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 0); 218 mNiNotification.setLatestEventInfo(mContext, title, message, pi); 219 220 if (visible) { 221 notificationManager.notify(notif.notificationId, mNiNotification); 222 } else { 223 notificationManager.cancel(notif.notificationId); 224 } 225 } 226 227 // Opens the notification dialog and waits for user input 228 private void openNiDialog(GpsNiNotification notif) 229 { 230 Intent intent = getDlgIntent(notif); 231 232 if (DEBUG) Log.d(TAG, "openNiDialog, notifyId: " + notif.notificationId + 233 ", requestorId: " + notif.requestorId + 234 ", text: " + notif.text); 235 236 mContext.startActivity(intent); 237 } 238 239 // Construct the intent for bringing up the dialog activity, which shows the 240 // notification and takes user input 241 private Intent getDlgIntent(GpsNiNotification notif) 242 { 243 Intent intent = new Intent(); 244 String title = getDialogTitle(notif, mContext); 245 String message = getDialogMessage(notif, mContext); 246 247 // directly bring up the NI activity 248 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 249 intent.setClass(mContext, com.android.internal.app.NetInitiatedActivity.class); 250 251 // put data in the intent 252 intent.putExtra(NI_INTENT_KEY_NOTIF_ID, notif.notificationId); 253 intent.putExtra(NI_INTENT_KEY_TITLE, title); 254 intent.putExtra(NI_INTENT_KEY_MESSAGE, message); 255 intent.putExtra(NI_INTENT_KEY_TIMEOUT, notif.timeout); 256 intent.putExtra(NI_INTENT_KEY_DEFAULT_RESPONSE, notif.defaultResponse); 257 258 if (DEBUG) Log.d(TAG, "generateIntent, title: " + title + ", message: " + message + 259 ", timeout: " + notif.timeout); 260 261 return intent; 262 } 263 264 // Converts a string (or Hex string) to a char array 265 static byte[] stringToByteArray(String original, boolean isHex) 266 { 267 int length = isHex ? original.length() / 2 : original.length(); 268 byte[] output = new byte[length]; 269 int i; 270 271 if (isHex) 272 { 273 for (i = 0; i < length; i++) 274 { 275 output[i] = (byte) Integer.parseInt(original.substring(i*2, i*2+2), 16); 276 } 277 } 278 else { 279 for (i = 0; i < length; i++) 280 { 281 output[i] = (byte) original.charAt(i); 282 } 283 } 284 285 return output; 286 } 287 288 /** 289 * Unpacks an byte array containing 7-bit packed characters into a String. 290 * 291 * @param input a 7-bit packed char array 292 * @return the unpacked String 293 */ 294 static String decodeGSMPackedString(byte[] input) 295 { 296 final char CHAR_CR = 0x0D; 297 int nStridx = 0; 298 int nPckidx = 0; 299 int num_bytes = input.length; 300 int cPrev = 0; 301 int cCurr = 0; 302 byte nShift; 303 byte nextChar; 304 byte[] stringBuf = new byte[input.length * 2]; 305 String result = ""; 306 307 while(nPckidx < num_bytes) 308 { 309 nShift = (byte) (nStridx & 0x07); 310 cCurr = input[nPckidx++]; 311 if (cCurr < 0) cCurr += 256; 312 313 /* A 7-bit character can be split at the most between two bytes of packed 314 ** data. 315 */ 316 nextChar = (byte) (( (cCurr << nShift) | (cPrev >> (8-nShift)) ) & 0x7F); 317 stringBuf[nStridx++] = nextChar; 318 319 /* Special case where the whole of the next 7-bit character fits inside 320 ** the current byte of packed data. 321 */ 322 if(nShift == 6) 323 { 324 /* If the next 7-bit character is a CR (0x0D) and it is the last 325 ** character, then it indicates a padding character. Drop it. 326 */ 327 if (nPckidx == num_bytes || (cCurr >> 1) == CHAR_CR) 328 { 329 break; 330 } 331 332 nextChar = (byte) (cCurr >> 1); 333 stringBuf[nStridx++] = nextChar; 334 } 335 336 cPrev = cCurr; 337 } 338 339 try{ 340 result = new String(stringBuf, 0, nStridx, "US-ASCII"); 341 } 342 catch (UnsupportedEncodingException e) 343 { 344 Log.e(TAG, e.getMessage()); 345 } 346 347 return result; 348 } 349 350 static String decodeUTF8String(byte[] input) 351 { 352 String decoded = ""; 353 try { 354 decoded = new String(input, "UTF-8"); 355 } 356 catch (UnsupportedEncodingException e) 357 { 358 Log.e(TAG, e.getMessage()); 359 } 360 return decoded; 361 } 362 363 static String decodeUCS2String(byte[] input) 364 { 365 String decoded = ""; 366 try { 367 decoded = new String(input, "UTF-16"); 368 } 369 catch (UnsupportedEncodingException e) 370 { 371 Log.e(TAG, e.getMessage()); 372 } 373 return decoded; 374 } 375 376 /** Decode NI string 377 * 378 * @param original The text string to be decoded 379 * @param isHex Specifies whether the content of the string has been encoded as a Hex string. Encoding 380 * a string as Hex can allow zeros inside the coded text. 381 * @param coding Specifies the coding scheme of the string, such as GSM, UTF8, UCS2, etc. This coding scheme 382 * needs to match those used passed to HAL from the native GPS driver. Decoding is done according 383 * to the <code> coding </code>, after a Hex string is decoded. Generally, if the 384 * notification strings don't need further decoding, <code> coding </code> encoding can be 385 * set to -1, and <code> isHex </code> can be false. 386 * @return the decoded string 387 */ 388 static private String decodeString(String original, boolean isHex, int coding) 389 { 390 String decoded = original; 391 byte[] input = stringToByteArray(original, isHex); 392 393 switch (coding) { 394 case GPS_ENC_NONE: 395 decoded = original; 396 break; 397 398 case GPS_ENC_SUPL_GSM_DEFAULT: 399 decoded = decodeGSMPackedString(input); 400 break; 401 402 case GPS_ENC_SUPL_UTF8: 403 decoded = decodeUTF8String(input); 404 break; 405 406 case GPS_ENC_SUPL_UCS2: 407 decoded = decodeUCS2String(input); 408 break; 409 410 case GPS_ENC_UNKNOWN: 411 decoded = original; 412 break; 413 414 default: 415 Log.e(TAG, "Unknown encoding " + coding + " for NI text " + original); 416 break; 417 } 418 return decoded; 419 } 420 421 // change this to configure notification display 422 static private String getNotifTicker(GpsNiNotification notif, Context context) 423 { 424 String ticker = String.format(context.getString(R.string.gpsNotifTicker), 425 decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding), 426 decodeString(notif.text, mIsHexInput, notif.textEncoding)); 427 return ticker; 428 } 429 430 // change this to configure notification display 431 static private String getNotifTitle(GpsNiNotification notif, Context context) 432 { 433 String title = String.format(context.getString(R.string.gpsNotifTitle)); 434 return title; 435 } 436 437 // change this to configure notification display 438 static private String getNotifMessage(GpsNiNotification notif, Context context) 439 { 440 String message = String.format(context.getString(R.string.gpsNotifMessage), 441 decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding), 442 decodeString(notif.text, mIsHexInput, notif.textEncoding)); 443 return message; 444 } 445 446 // change this to configure dialog display (for verification) 447 static public String getDialogTitle(GpsNiNotification notif, Context context) 448 { 449 return getNotifTitle(notif, context); 450 } 451 452 // change this to configure dialog display (for verification) 453 static private String getDialogMessage(GpsNiNotification notif, Context context) 454 { 455 return getNotifMessage(notif, context); 456 } 457 458} 459