OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / frameworks / base / location / java / com / android / internal / location / GpsNetInitiatedHandler.java
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.location.LocationManager;
27 import android.os.Bundle;
28 import android.os.RemoteException;
29 import android.util.Log;
30
31 /**
32  * A GPS Network-initiated Handler class used by LocationManager.
33  *
34  * {@hide}
35  */
36 public class GpsNetInitiatedHandler {
37
38     private static final String TAG = "GpsNetInitiatedHandler";
39
40     private static final boolean DEBUG = true;
41     private static final boolean VERBOSE = false;
42
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";
45     
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";
52     
53     // the extra command to send NI response to GpsLocationProvider
54     public static final String NI_RESPONSE_EXTRA_CMD = "send_ni_response";
55     
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";
59     
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;
64     
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;    
69     
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;
74     
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;
81     
82     private final Context mContext;
83     
84     // parent gps location provider
85     private final LocationManager mLocationManager;
86     
87     // configuration of notificaiton behavior
88     private boolean mPlaySounds = false;
89     private boolean visible = true;
90     private boolean mPopupImmediately = true;
91     
92     // Set to true if string from HAL is encoded as Hex, e.g., "3F0039"    
93     static private boolean mIsHexInput = true;
94         
95     public static class GpsNiNotification
96     {
97         public int notificationId;
98         public int niType;
99         public boolean needNotify;
100         public boolean needVerify;
101         public boolean privacyOverride;
102         public int timeout;
103         public int defaultResponse;
104         public String requestorId;
105         public String text;
106         public int requestorIdEncoding;
107         public int textEncoding;
108         public Bundle extras;
109     };
110     
111     public static class GpsNiResponse {
112         /* User reponse, one of the values in GpsUserResponseType */
113         int userResponse;
114         /* Optional extra data to pass with the user response */
115         Bundle extras;
116     };
117     
118     /**
119      * The notification that is shown when a network-initiated notification
120      * (and verification) event is received. 
121      * <p>
122      * This is lazily created, so use {@link #setNINotification()}.
123      */
124     private Notification mNiNotification;
125     
126     public GpsNetInitiatedHandler(Context context) {
127         mContext = context;
128         mLocationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE);
129     }
130     
131     // Handles NI events from HAL
132     public void handleNiNotification(GpsNiNotification notif)
133     {
134         if (DEBUG) Log.d(TAG, "handleNiNotification" + " notificationId: " + notif.notificationId
135                 + " requestorId: " + notif.requestorId + " text: " + notif.text);
136
137         // Notify and verify with immediate pop-up
138         if (notif.needNotify && notif.needVerify && mPopupImmediately)
139         {
140             // Popup the dialog box now
141             openNiDialog(notif);
142         }
143
144         // Notify only, or delayed pop-up (change mPopupImmediately to FALSE)
145         if (notif.needNotify && !notif.needVerify ||
146             notif.needNotify && notif.needVerify && !mPopupImmediately)
147         {
148             // Show the notification
149
150             // if mPopupImmediately == FALSE and needVerify == TRUE, a dialog will be opened
151             // when the user opens the notification message
152
153             setNiNotification(notif);
154         }
155
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)
160         {
161             mLocationManager.sendNiResponse(notif.notificationId, GPS_NI_RESPONSE_ACCEPT);
162         }
163
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.
168         //
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.
171         //
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.
174         //
175     }
176
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) {
182             return;
183         }
184
185         String title = getNotifTitle(notif);
186         String message = getNotifMessage(notif);
187
188         if (DEBUG) Log.d(TAG, "setNiNotification, notifyId: " + notif.notificationId +
189                 ", title: " + title +
190                 ", message: " + message);
191
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;
197         }
198
199         if (mPlaySounds) {
200             mNiNotification.defaults |= Notification.DEFAULT_SOUND;
201         } else {
202             mNiNotification.defaults &= ~Notification.DEFAULT_SOUND;
203         }        
204
205         mNiNotification.flags = Notification.FLAG_ONGOING_EVENT;
206         mNiNotification.tickerText = getNotifTicker(notif);
207
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);
212
213         if (visible) {
214             notificationManager.notify(notif.notificationId, mNiNotification);
215         } else {
216             notificationManager.cancel(notif.notificationId);
217         }
218     }
219
220     // Opens the notification dialog and waits for user input
221     private void openNiDialog(GpsNiNotification notif)
222     {
223         Intent intent = getDlgIntent(notif);
224
225         if (DEBUG) Log.d(TAG, "openNiDialog, notifyId: " + notif.notificationId +
226                 ", requestorId: " + notif.requestorId +
227                 ", text: " + notif.text);
228
229         mContext.startActivity(intent);
230     }
231
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)
235     {
236         Intent intent = new Intent();
237         String title = getDialogTitle(notif);
238         String message = getDialogMessage(notif);
239
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);
243
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);
250
251         if (DEBUG) Log.d(TAG, "generateIntent, title: " + title + ", message: " + message +
252                 ", timeout: " + notif.timeout);
253
254         return intent;
255     }
256
257     // Converts a string (or Hex string) to a char array
258     static byte[] stringToByteArray(String original, boolean isHex)
259     {
260         int length = isHex ? original.length() / 2 : original.length();
261         byte[] output = new byte[length];
262         int i;
263
264         if (isHex)
265         {
266             for (i = 0; i < length; i++)
267             {
268                 output[i] = (byte) Integer.parseInt(original.substring(i*2, i*2+2), 16);
269             }
270         }
271         else {
272             for (i = 0; i < length; i++)
273             {
274                 output[i] = (byte) original.charAt(i);
275             }
276         }
277
278         return output;
279     }
280
281     /**
282      * Unpacks an byte array containing 7-bit packed characters into a String.
283      *
284      * @param input a 7-bit packed char array
285      * @return the unpacked String
286      */
287     static String decodeGSMPackedString(byte[] input)
288     {
289         final char CHAR_CR = 0x0D;
290         int nStridx = 0;
291         int nPckidx = 0;
292         int num_bytes = input.length;
293         int cPrev = 0;
294         int cCurr = 0;
295         byte nShift;
296         byte nextChar;
297         byte[] stringBuf = new byte[input.length * 2];
298         String result = "";
299
300         while(nPckidx < num_bytes)
301         {
302             nShift = (byte) (nStridx & 0x07);
303             cCurr = input[nPckidx++];
304             if (cCurr < 0) cCurr += 256;
305
306             /* A 7-bit character can be split at the most between two bytes of packed
307              ** data.
308              */
309             nextChar = (byte) (( (cCurr << nShift) | (cPrev >> (8-nShift)) ) & 0x7F);
310             stringBuf[nStridx++] = nextChar;
311
312             /* Special case where the whole of the next 7-bit character fits inside
313              ** the current byte of packed data.
314              */
315             if(nShift == 6)
316             {
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.
319                  */
320                 if (nPckidx == num_bytes || (cCurr >> 1) == CHAR_CR)
321                 {
322                     break;
323                 }
324
325                 nextChar = (byte) (cCurr >> 1);
326                 stringBuf[nStridx++] = nextChar;
327             }
328
329             cPrev = cCurr;
330         }
331
332         try {
333             result = new String(stringBuf, 0, nStridx, "US-ASCII");
334         }
335         catch (UnsupportedEncodingException e)
336         {
337             Log.e(TAG, e.getMessage());
338         }
339
340         return result;
341     }
342
343     static String decodeUTF8String(byte[] input)
344     {
345         String decoded = "";
346         try {
347             decoded = new String(input, "UTF-8");
348         }
349         catch (UnsupportedEncodingException e)
350         {
351             Log.e(TAG, e.getMessage());
352         }
353         return decoded;
354     }
355
356     static String decodeUCS2String(byte[] input)
357     {
358         String decoded = "";
359         try {
360             decoded = new String(input, "UTF-16");
361         }
362         catch (UnsupportedEncodingException e)
363         {
364             Log.e(TAG, e.getMessage());
365         }
366         return decoded;
367     }
368
369     /** Decode NI string
370      *
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
380      */
381     static private String decodeString(String original, boolean isHex, int coding)
382     {
383         String decoded = original;
384         byte[] input = stringToByteArray(original, isHex);
385
386         switch (coding) {
387         case GPS_ENC_NONE:
388             decoded = original;
389             break;
390
391         case GPS_ENC_SUPL_GSM_DEFAULT:
392             decoded = decodeGSMPackedString(input);
393             break;
394
395         case GPS_ENC_SUPL_UTF8:
396             decoded = decodeUTF8String(input);
397             break;
398
399         case GPS_ENC_SUPL_UCS2:
400             decoded = decodeUCS2String(input);
401             break;
402
403         case GPS_ENC_UNKNOWN:
404             decoded = original;
405             break;
406
407         default:
408             Log.e(TAG, "Unknown encoding " + coding + " for NI text " + original);
409             break;
410         }
411         return decoded;
412     }
413
414     // change this to configure notification display
415     static private String getNotifTicker(GpsNiNotification notif)
416     {
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));
420         return ticker;
421     }
422
423     // change this to configure notification display
424     static private String getNotifTitle(GpsNiNotification notif)
425     {
426         String title = String.format("Position Request");
427         return title;
428     }
429
430     // change this to configure notification display
431     static private String getNotifMessage(GpsNiNotification notif)
432     {
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));
437         return message;
438     }       
439
440     // change this to configure dialog display (for verification)
441     static public String getDialogTitle(GpsNiNotification notif)
442     {
443         return getNotifTitle(notif);
444     }
445
446     // change this to configure dialog display (for verification)
447     static private String getDialogMessage(GpsNiNotification notif)
448     {
449         return getNotifMessage(notif);
450     }
451
452 }