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