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