2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.internal.location;
19 import java.io.UnsupportedEncodingException;
21 import android.app.Notification;
22 import android.app.NotificationManager;
23 import android.app.PendingIntent;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.location.LocationManager;
27 import android.os.Bundle;
28 import android.os.RemoteException;
29 import android.util.Log;
32 * A GPS Network-initiated Handler class used by LocationManager.
36 public class GpsNetInitiatedHandler {
38 private static final String TAG = "GpsNetInitiatedHandler";
40 private static final boolean DEBUG = true;
41 private static final boolean VERBOSE = false;
43 // NI verify activity for bringing up UI (not used yet)
44 public static final String ACTION_NI_VERIFY = "android.intent.action.NETWORK_INITIATED_VERIFY";
46 // string constants for defining data fields in NI Intent
47 public static final String NI_INTENT_KEY_NOTIF_ID = "notif_id";
48 public static final String NI_INTENT_KEY_TITLE = "title";
49 public static final String NI_INTENT_KEY_MESSAGE = "message";
50 public static final String NI_INTENT_KEY_TIMEOUT = "timeout";
51 public static final String NI_INTENT_KEY_DEFAULT_RESPONSE = "default_resp";
53 // the extra command to send NI response to GpsLocationProvider
54 public static final String NI_RESPONSE_EXTRA_CMD = "send_ni_response";
56 // the extra command parameter names in the Bundle
57 public static final String NI_EXTRA_CMD_NOTIF_ID = "notif_id";
58 public static final String NI_EXTRA_CMD_RESPONSE = "response";
60 // these need to match GpsNiType constants in gps_ni.h
61 public static final int GPS_NI_TYPE_VOICE = 1;
62 public static final int GPS_NI_TYPE_UMTS_SUPL = 2;
63 public static final int GPS_NI_TYPE_UMTS_CTRL_PLANE = 3;
65 // these need to match GpsUserResponseType constants in gps_ni.h
66 public static final int GPS_NI_RESPONSE_ACCEPT = 1;
67 public static final int GPS_NI_RESPONSE_DENY = 2;
68 public static final int GPS_NI_RESPONSE_NORESP = 3;
70 // these need to match GpsNiNotifyFlags constants in gps_ni.h
71 public static final int GPS_NI_NEED_NOTIFY = 0x0001;
72 public static final int GPS_NI_NEED_VERIFY = 0x0002;
73 public static final int GPS_NI_PRIVACY_OVERRIDE = 0x0004;
75 // these need to match GpsNiEncodingType in gps_ni.h
76 public static final int GPS_ENC_NONE = 0;
77 public static final int GPS_ENC_SUPL_GSM_DEFAULT = 1;
78 public static final int GPS_ENC_SUPL_UTF8 = 2;
79 public static final int GPS_ENC_SUPL_UCS2 = 3;
80 public static final int GPS_ENC_UNKNOWN = -1;
82 private final Context mContext;
84 // parent gps location provider
85 private final LocationManager mLocationManager;
87 // configuration of notificaiton behavior
88 private boolean mPlaySounds = false;
89 private boolean visible = true;
90 private boolean mPopupImmediately = true;
92 // Set to true if string from HAL is encoded as Hex, e.g., "3F0039"
93 static private boolean mIsHexInput = true;
95 public static class GpsNiNotification
97 public int notificationId;
99 public boolean needNotify;
100 public boolean needVerify;
101 public boolean privacyOverride;
103 public int defaultResponse;
104 public String requestorId;
106 public int requestorIdEncoding;
107 public int textEncoding;
108 public Bundle extras;
111 public static class GpsNiResponse {
112 /* User reponse, one of the values in GpsUserResponseType */
114 /* Optional extra data to pass with the user response */
119 * The notification that is shown when a network-initiated notification
120 * (and verification) event is received.
122 * This is lazily created, so use {@link #setNINotification()}.
124 private Notification mNiNotification;
126 public GpsNetInitiatedHandler(Context context) {
128 mLocationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE);
131 // Handles NI events from HAL
132 public void handleNiNotification(GpsNiNotification notif)
134 if (DEBUG) Log.d(TAG, "handleNiNotification" + " notificationId: " + notif.notificationId
135 + " requestorId: " + notif.requestorId + " text: " + notif.text);
137 // Notify and verify with immediate pop-up
138 if (notif.needNotify && notif.needVerify && mPopupImmediately)
140 // Popup the dialog box now
144 // Notify only, or delayed pop-up (change mPopupImmediately to FALSE)
145 if (notif.needNotify && !notif.needVerify ||
146 notif.needNotify && notif.needVerify && !mPopupImmediately)
148 // Show the notification
150 // if mPopupImmediately == FALSE and needVerify == TRUE, a dialog will be opened
151 // when the user opens the notification message
153 setNiNotification(notif);
156 // ACCEPT cases: 1. Notify, no verify; 2. no notify, no verify; 3. privacy override.
157 if ( notif.needNotify && !notif.needVerify ||
158 !notif.needNotify && !notif.needVerify ||
159 notif.privacyOverride)
161 mLocationManager.sendNiResponse(notif.notificationId, GPS_NI_RESPONSE_ACCEPT);
164 //////////////////////////////////////////////////////////////////////////
165 // A note about timeout
166 // According to the protocol, in the need_notify and need_verify case,
167 // a default response should be sent when time out.
169 // In some GPS hardware, the GPS driver (under HAL) can handle the timeout case
170 // and this class GpsNetInitiatedHandler does not need to do anything.
172 // However, the UI should at least close the dialog when timeout. Further,
173 // for more general handling, timeout response should be added to the Handler here.
177 // Sets the NI notification.
178 private synchronized void setNiNotification(GpsNiNotification notif) {
179 NotificationManager notificationManager = (NotificationManager) mContext
180 .getSystemService(Context.NOTIFICATION_SERVICE);
181 if (notificationManager == null) {
185 String title = getNotifTitle(notif);
186 String message = getNotifMessage(notif);
188 if (DEBUG) Log.d(TAG, "setNiNotification, notifyId: " + notif.notificationId +
189 ", title: " + title +
190 ", message: " + message);
192 // Construct Notification
193 if (mNiNotification == null) {
194 mNiNotification = new Notification();
195 mNiNotification.icon = com.android.internal.R.drawable.stat_sys_gps_on; /* Change notification icon here */
196 mNiNotification.when = 0;
200 mNiNotification.defaults |= Notification.DEFAULT_SOUND;
202 mNiNotification.defaults &= ~Notification.DEFAULT_SOUND;
205 mNiNotification.flags = Notification.FLAG_ONGOING_EVENT;
206 mNiNotification.tickerText = getNotifTicker(notif);
208 // if not to popup dialog immediately, pending intent will open the dialog
209 Intent intent = !mPopupImmediately ? getDlgIntent(notif) : new Intent();
210 PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
211 mNiNotification.setLatestEventInfo(mContext, title, message, pi);
214 notificationManager.notify(notif.notificationId, mNiNotification);
216 notificationManager.cancel(notif.notificationId);
220 // Opens the notification dialog and waits for user input
221 private void openNiDialog(GpsNiNotification notif)
223 Intent intent = getDlgIntent(notif);
225 if (DEBUG) Log.d(TAG, "openNiDialog, notifyId: " + notif.notificationId +
226 ", requestorId: " + notif.requestorId +
227 ", text: " + notif.text);
229 mContext.startActivity(intent);
232 // Construct the intent for bringing up the dialog activity, which shows the
233 // notification and takes user input
234 private Intent getDlgIntent(GpsNiNotification notif)
236 Intent intent = new Intent();
237 String title = getDialogTitle(notif);
238 String message = getDialogMessage(notif);
240 // directly bring up the NI activity
241 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
242 intent.setClass(mContext, com.android.internal.app.NetInitiatedActivity.class);
244 // put data in the intent
245 intent.putExtra(NI_INTENT_KEY_NOTIF_ID, notif.notificationId);
246 intent.putExtra(NI_INTENT_KEY_TITLE, title);
247 intent.putExtra(NI_INTENT_KEY_MESSAGE, message);
248 intent.putExtra(NI_INTENT_KEY_TIMEOUT, notif.timeout);
249 intent.putExtra(NI_INTENT_KEY_DEFAULT_RESPONSE, notif.defaultResponse);
251 if (DEBUG) Log.d(TAG, "generateIntent, title: " + title + ", message: " + message +
252 ", timeout: " + notif.timeout);
257 // Converts a string (or Hex string) to a char array
258 static byte[] stringToByteArray(String original, boolean isHex)
260 int length = isHex ? original.length() / 2 : original.length();
261 byte[] output = new byte[length];
266 for (i = 0; i < length; i++)
268 output[i] = (byte) Integer.parseInt(original.substring(i*2, i*2+2), 16);
272 for (i = 0; i < length; i++)
274 output[i] = (byte) original.charAt(i);
282 * Unpacks an byte array containing 7-bit packed characters into a String.
284 * @param input a 7-bit packed char array
285 * @return the unpacked String
287 static String decodeGSMPackedString(byte[] input)
289 final char CHAR_CR = 0x0D;
292 int num_bytes = input.length;
297 byte[] stringBuf = new byte[input.length * 2];
300 while(nPckidx < num_bytes)
302 nShift = (byte) (nStridx & 0x07);
303 cCurr = input[nPckidx++];
304 if (cCurr < 0) cCurr += 256;
306 /* A 7-bit character can be split at the most between two bytes of packed
309 nextChar = (byte) (( (cCurr << nShift) | (cPrev >> (8-nShift)) ) & 0x7F);
310 stringBuf[nStridx++] = nextChar;
312 /* Special case where the whole of the next 7-bit character fits inside
313 ** the current byte of packed data.
317 /* If the next 7-bit character is a CR (0x0D) and it is the last
318 ** character, then it indicates a padding character. Drop it.
320 if (nPckidx == num_bytes || (cCurr >> 1) == CHAR_CR)
325 nextChar = (byte) (cCurr >> 1);
326 stringBuf[nStridx++] = nextChar;
333 result = new String(stringBuf, 0, nStridx, "US-ASCII");
335 catch (UnsupportedEncodingException e)
337 Log.e(TAG, e.getMessage());
343 static String decodeUTF8String(byte[] input)
347 decoded = new String(input, "UTF-8");
349 catch (UnsupportedEncodingException e)
351 Log.e(TAG, e.getMessage());
356 static String decodeUCS2String(byte[] input)
360 decoded = new String(input, "UTF-16");
362 catch (UnsupportedEncodingException e)
364 Log.e(TAG, e.getMessage());
371 * @param original The text string to be decoded
372 * @param isHex Specifies whether the content of the string has been encoded as a Hex string. Encoding
373 * a string as Hex can allow zeros inside the coded text.
374 * @param coding Specifies the coding scheme of the string, such as GSM, UTF8, UCS2, etc. This coding scheme
375 * needs to match those used passed to HAL from the native GPS driver. Decoding is done according
376 * to the <code> coding </code>, after a Hex string is decoded. Generally, if the
377 * notification strings don't need further decoding, <code> coding </code> encoding can be
378 * set to -1, and <code> isHex </code> can be false.
379 * @return the decoded string
381 static private String decodeString(String original, boolean isHex, int coding)
383 String decoded = original;
384 byte[] input = stringToByteArray(original, isHex);
391 case GPS_ENC_SUPL_GSM_DEFAULT:
392 decoded = decodeGSMPackedString(input);
395 case GPS_ENC_SUPL_UTF8:
396 decoded = decodeUTF8String(input);
399 case GPS_ENC_SUPL_UCS2:
400 decoded = decodeUCS2String(input);
403 case GPS_ENC_UNKNOWN:
408 Log.e(TAG, "Unknown encoding " + coding + " for NI text " + original);
414 // change this to configure notification display
415 static private String getNotifTicker(GpsNiNotification notif)
417 String ticker = String.format("Position request! ReqId: [%s] ClientName: [%s]",
418 decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding),
419 decodeString(notif.text, mIsHexInput, notif.textEncoding));
423 // change this to configure notification display
424 static private String getNotifTitle(GpsNiNotification notif)
426 String title = String.format("Position Request");
430 // change this to configure notification display
431 static private String getNotifMessage(GpsNiNotification notif)
433 String message = String.format(
434 "NI Request received from [%s] for client [%s]!",
435 decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding),
436 decodeString(notif.text, mIsHexInput, notif.textEncoding));
440 // change this to configure dialog display (for verification)
441 static public String getDialogTitle(GpsNiNotification notif)
443 return getNotifTitle(notif);
446 // change this to configure dialog display (for verification)
447 static private String getDialogMessage(GpsNiNotification notif)
449 return getNotifMessage(notif);