• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 java.io.UnsupportedEncodingException;
20 
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.os.Bundle;
27 import android.os.RemoteException;
28 import android.util.Log;
29 
30 /**
31  * A GPS Network-initiated Handler class used by LocationManager.
32  *
33  * {@hide}
34  */
35 public 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 
GpsNetInitiatedHandler(Context context, GpsLocationProvider gpsLocationProvider)125     public GpsNetInitiatedHandler(Context context, GpsLocationProvider gpsLocationProvider) {
126     	mContext = context;
127     	mGpsLocationProvider = gpsLocationProvider;
128     }
129 
130     // Handles NI events from HAL
handleNiNotification(GpsNiNotification notif)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.
setNiNotification(GpsNiNotification notif)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
openNiDialog(GpsNiNotification notif)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
getDlgIntent(GpsNiNotification notif)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
stringToByteArray(String original, boolean isHex)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      */
decodeGSMPackedString(byte[] input)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 
decodeUTF8String(byte[] input)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 
decodeUCS2String(byte[] input)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      */
decodeString(String original, boolean isHex, int coding)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
getNotifTicker(GpsNiNotification notif)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
getNotifTitle(GpsNiNotification notif)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
getNotifMessage(GpsNiNotification notif)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)
getDialogTitle(GpsNiNotification notif)446     static public String getDialogTitle(GpsNiNotification notif)
447     {
448     	return getNotifTitle(notif);
449     }
450 
451     // change this to configure dialog display (for verification)
getDialogMessage(GpsNiNotification notif)452     static private String getDialogMessage(GpsNiNotification notif)
453     {
454     	return getNotifMessage(notif);
455     }
456 
457 }
458