OSDN Git Service

9bd59940e37b3b638d5babb0bc746ba7bff975fb
[android-x86/frameworks-base.git] / 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.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.location.LocationManager;
29 import android.location.INetInitiatedListener;
30 import android.telephony.TelephonyManager;
31 import android.telephony.PhoneNumberUtils;
32 import android.telephony.PhoneStateListener;
33 import android.os.Bundle;
34 import android.os.RemoteException;
35 import android.os.UserHandle;
36 import android.os.SystemProperties;
37 import android.util.Log;
38
39 import com.android.internal.notification.SystemNotificationChannels;
40 import com.android.internal.R;
41 import com.android.internal.telephony.GsmAlphabet;
42 import com.android.internal.telephony.TelephonyProperties;
43
44 /**
45  * A GPS Network-initiated Handler class used by LocationManager.
46  *
47  * {@hide}
48  */
49 public class GpsNetInitiatedHandler {
50
51     private static final String TAG = "GpsNetInitiatedHandler";
52
53     private static final boolean DEBUG = true;
54     private static final boolean VERBOSE = false;
55
56     // NI verify activity for bringing up UI (not used yet)
57     public static final String ACTION_NI_VERIFY = "android.intent.action.NETWORK_INITIATED_VERIFY";
58
59     // string constants for defining data fields in NI Intent
60     public static final String NI_INTENT_KEY_NOTIF_ID = "notif_id";
61     public static final String NI_INTENT_KEY_TITLE = "title";
62     public static final String NI_INTENT_KEY_MESSAGE = "message";
63     public static final String NI_INTENT_KEY_TIMEOUT = "timeout";
64     public static final String NI_INTENT_KEY_DEFAULT_RESPONSE = "default_resp";
65
66     // the extra command to send NI response to GnssLocationProvider
67     public static final String NI_RESPONSE_EXTRA_CMD = "send_ni_response";
68
69     // the extra command parameter names in the Bundle
70     public static final String NI_EXTRA_CMD_NOTIF_ID = "notif_id";
71     public static final String NI_EXTRA_CMD_RESPONSE = "response";
72
73     // these need to match GpsNiType constants in gps_ni.h
74     public static final int GPS_NI_TYPE_VOICE = 1;
75     public static final int GPS_NI_TYPE_UMTS_SUPL = 2;
76     public static final int GPS_NI_TYPE_UMTS_CTRL_PLANE = 3;
77     public static final int GPS_NI_TYPE_EMERGENCY_SUPL = 4;
78
79     // these need to match GpsUserResponseType constants in gps_ni.h
80     public static final int GPS_NI_RESPONSE_ACCEPT = 1;
81     public static final int GPS_NI_RESPONSE_DENY = 2;
82     public static final int GPS_NI_RESPONSE_NORESP = 3;
83     public static final int GPS_NI_RESPONSE_IGNORE = 4;
84
85     // these need to match GpsNiNotifyFlags constants in gps_ni.h
86     public static final int GPS_NI_NEED_NOTIFY = 0x0001;
87     public static final int GPS_NI_NEED_VERIFY = 0x0002;
88     public static final int GPS_NI_PRIVACY_OVERRIDE = 0x0004;
89
90     // these need to match GpsNiEncodingType in gps_ni.h
91     public static final int GPS_ENC_NONE = 0;
92     public static final int GPS_ENC_SUPL_GSM_DEFAULT = 1;
93     public static final int GPS_ENC_SUPL_UTF8 = 2;
94     public static final int GPS_ENC_SUPL_UCS2 = 3;
95     public static final int GPS_ENC_UNKNOWN = -1;
96
97     private final Context mContext;
98     private final TelephonyManager mTelephonyManager;
99     private final PhoneStateListener mPhoneStateListener;
100
101     // parent gps location provider
102     private final LocationManager mLocationManager;
103
104     // configuration of notificaiton behavior
105     private boolean mPlaySounds = false;
106     private boolean mPopupImmediately = true;
107
108     // read the SUPL_ES form gps.conf
109     private volatile boolean mIsSuplEsEnabled;
110
111     // Set to true if the phone is having emergency call.
112     private volatile boolean mIsInEmergency;
113
114     // If Location function is enabled.
115     private volatile boolean mIsLocationEnabled = false;
116
117     private final INetInitiatedListener mNetInitiatedListener;
118
119     // Set to true if string from HAL is encoded as Hex, e.g., "3F0039"
120     static private boolean mIsHexInput = true;
121
122     public static class GpsNiNotification
123     {
124         public int notificationId;
125         public int niType;
126         public boolean needNotify;
127         public boolean needVerify;
128         public boolean privacyOverride;
129         public int timeout;
130         public int defaultResponse;
131         public String requestorId;
132         public String text;
133         public int requestorIdEncoding;
134         public int textEncoding;
135     };
136
137     public static class GpsNiResponse {
138         /* User response, one of the values in GpsUserResponseType */
139         int userResponse;
140     };
141
142     private final BroadcastReceiver mBroadcastReciever = new BroadcastReceiver() {
143
144         @Override public void onReceive(Context context, Intent intent) {
145             String action = intent.getAction();
146             if (action.equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
147                 String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
148                 /*
149                    Emergency Mode is when during emergency call or in emergency call back mode.
150                    For checking if it is during emergency call:
151                        mIsInEmergency records if the phone is in emergency call or not. It will
152                        be set to true when the phone is having emergency call, and then will
153                        be set to false by mPhoneStateListener when the emergency call ends.
154                    For checking if it is in emergency call back mode:
155                        Emergency call back mode will be checked by reading system properties
156                        when necessary: SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)
157                 */
158                 setInEmergency(PhoneNumberUtils.isEmergencyNumber(phoneNumber));
159                 if (DEBUG) Log.v(TAG, "ACTION_NEW_OUTGOING_CALL - " + getInEmergency());
160             } else if (action.equals(LocationManager.MODE_CHANGED_ACTION)) {
161                 updateLocationMode();
162                 if (DEBUG) Log.d(TAG, "location enabled :" + getLocationEnabled());
163             }
164         }
165     };
166
167     /**
168      * The notification that is shown when a network-initiated notification
169      * (and verification) event is received.
170      * <p>
171      * This is lazily created, so use {@link #setNINotification()}.
172      */
173     private Notification.Builder mNiNotificationBuilder;
174
175     public GpsNetInitiatedHandler(Context context,
176                                   INetInitiatedListener netInitiatedListener,
177                                   boolean isSuplEsEnabled) {
178         mContext = context;
179
180         if (netInitiatedListener == null) {
181             throw new IllegalArgumentException("netInitiatedListener is null");
182         } else {
183             mNetInitiatedListener = netInitiatedListener;
184         }
185
186         setSuplEsEnabled(isSuplEsEnabled);
187         mLocationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE);
188         updateLocationMode();
189         mTelephonyManager =
190             (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
191
192         mPhoneStateListener = new PhoneStateListener() {
193             @Override
194             public void onCallStateChanged(int state, String incomingNumber) {
195                 if (DEBUG) Log.d(TAG, "onCallStateChanged(): state is "+ state);
196                 // listening for emergency call ends
197                 if (state == TelephonyManager.CALL_STATE_IDLE) {
198                     setInEmergency(false);
199                 }
200             }
201         };
202         mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
203
204         IntentFilter intentFilter = new IntentFilter();
205         intentFilter.addAction(Intent.ACTION_NEW_OUTGOING_CALL);
206         intentFilter.addAction(LocationManager.MODE_CHANGED_ACTION);
207         mContext.registerReceiver(mBroadcastReciever, intentFilter);
208     }
209
210     public void setSuplEsEnabled(boolean isEnabled) {
211         mIsSuplEsEnabled = isEnabled;
212     }
213
214     public boolean getSuplEsEnabled() {
215         return mIsSuplEsEnabled;
216     }
217
218     /**
219      * Updates Location enabler based on location setting.
220      */
221     public void updateLocationMode() {
222         mIsLocationEnabled = mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
223     }
224
225     /**
226      * Checks if user agreed to use location.
227      */
228     public boolean getLocationEnabled() {
229         return mIsLocationEnabled;
230     }
231
232     // Note: Currently, there are two mechanisms involved to determine if a
233     // phone is in emergency mode:
234     // 1. If the user is making an emergency call, this is provided by activly
235     //    monitoring the outgoing phone number;
236     // 2. If the device is in a emergency callback state, this is provided by
237     //    system properties.
238     // If either one of above exists, the phone is considered in an emergency
239     // mode. Because of this complexity, we need to be careful about how to set
240     // and clear the emergency state.
241     public void setInEmergency(boolean isInEmergency) {
242         mIsInEmergency = isInEmergency;
243     }
244
245     public boolean getInEmergency() {
246         boolean isInEmergencyCallback = mTelephonyManager.getEmergencyCallbackMode();
247         return mIsInEmergency || isInEmergencyCallback;
248     }
249
250
251     // Handles NI events from HAL
252     public void handleNiNotification(GpsNiNotification notif) {
253         if (DEBUG) Log.d(TAG, "in handleNiNotification () :"
254                         + " notificationId: " + notif.notificationId
255                         + " requestorId: " + notif.requestorId
256                         + " text: " + notif.text
257                         + " mIsSuplEsEnabled" + getSuplEsEnabled()
258                         + " mIsLocationEnabled" + getLocationEnabled());
259
260         if (getSuplEsEnabled()) {
261             handleNiInEs(notif);
262         } else {
263             handleNi(notif);
264         }
265
266         //////////////////////////////////////////////////////////////////////////
267         //   A note about timeout
268         //   According to the protocol, in the need_notify and need_verify case,
269         //   a default response should be sent when time out.
270         //
271         //   In some GPS hardware, the GPS driver (under HAL) can handle the timeout case
272         //   and this class GpsNetInitiatedHandler does not need to do anything.
273         //
274         //   However, the UI should at least close the dialog when timeout. Further,
275         //   for more general handling, timeout response should be added to the Handler here.
276         //
277     }
278
279     // handle NI form HAL when SUPL_ES is disabled.
280     private void handleNi(GpsNiNotification notif) {
281         if (DEBUG) Log.d(TAG, "in handleNi () :"
282                         + " needNotify: " + notif.needNotify
283                         + " needVerify: " + notif.needVerify
284                         + " privacyOverride: " + notif.privacyOverride
285                         + " mPopupImmediately: " + mPopupImmediately
286                         + " mInEmergency: " + getInEmergency());
287
288         if (!getLocationEnabled() && !getInEmergency()) {
289             // Location is currently disabled, ignore all NI requests.
290             try {
291                 mNetInitiatedListener.sendNiResponse(notif.notificationId,
292                                                      GPS_NI_RESPONSE_IGNORE);
293             } catch (RemoteException e) {
294                 Log.e(TAG, "RemoteException in sendNiResponse");
295             }
296         }
297         if (notif.needNotify) {
298         // If NI does not need verify or the dialog is not requested
299         // to pop up immediately, the dialog box will not pop up.
300             if (notif.needVerify && mPopupImmediately) {
301                 // Popup the dialog box now
302                 openNiDialog(notif);
303             } else {
304                 // Show the notification
305                 setNiNotification(notif);
306             }
307         }
308         // ACCEPT cases: 1. Notify, no verify; 2. no notify, no verify;
309         // 3. privacy override.
310         if (!notif.needVerify || notif.privacyOverride) {
311             try {
312                 mNetInitiatedListener.sendNiResponse(notif.notificationId,
313                                                      GPS_NI_RESPONSE_ACCEPT);
314             } catch (RemoteException e) {
315                 Log.e(TAG, "RemoteException in sendNiResponse");
316             }
317         }
318     }
319
320     // handle NI from HAL when the SUPL_ES is enabled
321     private void handleNiInEs(GpsNiNotification notif) {
322
323         if (DEBUG) Log.d(TAG, "in handleNiInEs () :"
324                     + " niType: " + notif.niType
325                     + " notificationId: " + notif.notificationId);
326
327         // UE is in emergency mode when in emergency call mode or in emergency call back mode
328         /*
329            1. When SUPL ES bit is off and UE is not in emergency mode:
330                   Call handleNi() to do legacy behaviour.
331            2. When SUPL ES bit is on and UE is in emergency mode:
332                   Call handleNi() to do acceptance behaviour.
333            3. When SUPL ES bit is off but UE is in emergency mode:
334                   Ignore the emergency SUPL INIT.
335            4. When SUPL ES bit is on but UE is not in emergency mode:
336                   Ignore the emergency SUPL INIT.
337         */
338         boolean isNiTypeES = (notif.niType == GPS_NI_TYPE_EMERGENCY_SUPL);
339         if (isNiTypeES != getInEmergency()) {
340             try {
341                 mNetInitiatedListener.sendNiResponse(notif.notificationId,
342                                                      GPS_NI_RESPONSE_IGNORE);
343             } catch (RemoteException e) {
344                 Log.e(TAG, "RemoteException in sendNiResponse");
345             }
346         } else {
347             handleNi(notif);
348         }
349     }
350
351     // Sets the NI notification.
352     private synchronized void setNiNotification(GpsNiNotification notif) {
353         NotificationManager notificationManager = (NotificationManager) mContext
354                 .getSystemService(Context.NOTIFICATION_SERVICE);
355         if (notificationManager == null) {
356             return;
357         }
358
359         String title = getNotifTitle(notif, mContext);
360         String message = getNotifMessage(notif, mContext);
361
362         if (DEBUG) Log.d(TAG, "setNiNotification, notifyId: " + notif.notificationId +
363                 ", title: " + title +
364                 ", message: " + message);
365
366         // Construct Notification
367         if (mNiNotificationBuilder == null) {
368             mNiNotificationBuilder = new Notification.Builder(mContext,
369                 SystemNotificationChannels.NETWORK_ALERTS)
370                     .setSmallIcon(com.android.internal.R.drawable.stat_sys_gps_on)
371                     .setWhen(0)
372                     .setOngoing(true)
373                     .setAutoCancel(true)
374                     .setColor(mContext.getColor(
375                             com.android.internal.R.color.system_notification_accent_color));
376         }
377
378         if (mPlaySounds) {
379             mNiNotificationBuilder.setDefaults(Notification.DEFAULT_SOUND);
380         } else {
381             mNiNotificationBuilder.setDefaults(0);
382         }
383
384         // if not to popup dialog immediately, pending intent will open the dialog
385         Intent intent = !mPopupImmediately ? getDlgIntent(notif) : new Intent();
386         PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
387         mNiNotificationBuilder.setTicker(getNotifTicker(notif, mContext))
388                 .setContentTitle(title)
389                 .setContentText(message)
390                 .setContentIntent(pi);
391
392         notificationManager.notifyAsUser(null, notif.notificationId, mNiNotificationBuilder.build(),
393                 UserHandle.ALL);
394     }
395
396     // Opens the notification dialog and waits for user input
397     private void openNiDialog(GpsNiNotification notif)
398     {
399         Intent intent = getDlgIntent(notif);
400
401         if (DEBUG) Log.d(TAG, "openNiDialog, notifyId: " + notif.notificationId +
402                 ", requestorId: " + notif.requestorId +
403                 ", text: " + notif.text);
404
405         mContext.startActivity(intent);
406     }
407
408     // Construct the intent for bringing up the dialog activity, which shows the
409     // notification and takes user input
410     private Intent getDlgIntent(GpsNiNotification notif)
411     {
412         Intent intent = new Intent();
413         String title = getDialogTitle(notif, mContext);
414         String message = getDialogMessage(notif, mContext);
415
416         // directly bring up the NI activity
417         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
418         intent.setClass(mContext, com.android.internal.app.NetInitiatedActivity.class);
419
420         // put data in the intent
421         intent.putExtra(NI_INTENT_KEY_NOTIF_ID, notif.notificationId);
422         intent.putExtra(NI_INTENT_KEY_TITLE, title);
423         intent.putExtra(NI_INTENT_KEY_MESSAGE, message);
424         intent.putExtra(NI_INTENT_KEY_TIMEOUT, notif.timeout);
425         intent.putExtra(NI_INTENT_KEY_DEFAULT_RESPONSE, notif.defaultResponse);
426
427         if (DEBUG) Log.d(TAG, "generateIntent, title: " + title + ", message: " + message +
428                 ", timeout: " + notif.timeout);
429
430         return intent;
431     }
432
433     // Converts a string (or Hex string) to a char array
434     static byte[] stringToByteArray(String original, boolean isHex)
435     {
436         int length = isHex ? original.length() / 2 : original.length();
437         byte[] output = new byte[length];
438         int i;
439
440         if (isHex)
441         {
442             for (i = 0; i < length; i++)
443             {
444                 output[i] = (byte) Integer.parseInt(original.substring(i*2, i*2+2), 16);
445             }
446         }
447         else {
448             for (i = 0; i < length; i++)
449             {
450                 output[i] = (byte) original.charAt(i);
451             }
452         }
453
454         return output;
455     }
456
457     /**
458      * Unpacks an byte array containing 7-bit packed characters into a String.
459      *
460      * @param input a 7-bit packed char array
461      * @return the unpacked String
462      */
463     static String decodeGSMPackedString(byte[] input)
464     {
465         final char PADDING_CHAR = 0x00;
466         int lengthBytes = input.length;
467         int lengthSeptets = (lengthBytes * 8) / 7;
468         String decoded;
469
470         /* Special case where the last 7 bits in the last byte could hold a valid
471          * 7-bit character or a padding character. Drop the last 7-bit character
472          * if it is a padding character.
473          */
474         if (lengthBytes % 7 == 0) {
475             if (lengthBytes > 0) {
476                 if ((input[lengthBytes - 1] >> 1) == PADDING_CHAR) {
477                     lengthSeptets = lengthSeptets - 1;
478                 }
479             }
480         }
481
482         decoded = GsmAlphabet.gsm7BitPackedToString(input, 0, lengthSeptets);
483
484         // Return "" if decoding of GSM packed string fails
485         if (null == decoded) {
486             Log.e(TAG, "Decoding of GSM packed string failed");
487             decoded = "";
488         }
489
490         return decoded;
491     }
492
493     static String decodeUTF8String(byte[] input)
494     {
495         String decoded = "";
496         try {
497             decoded = new String(input, "UTF-8");
498         }
499         catch (UnsupportedEncodingException e)
500         {
501             throw new AssertionError();
502         }
503         return decoded;
504     }
505
506     static String decodeUCS2String(byte[] input)
507     {
508         String decoded = "";
509         try {
510             decoded = new String(input, "UTF-16");
511         }
512         catch (UnsupportedEncodingException e)
513         {
514             throw new AssertionError();
515         }
516         return decoded;
517     }
518
519     /** Decode NI string
520      *
521      * @param original   The text string to be decoded
522      * @param isHex      Specifies whether the content of the string has been encoded as a Hex string. Encoding
523      *                   a string as Hex can allow zeros inside the coded text.
524      * @param coding     Specifies the coding scheme of the string, such as GSM, UTF8, UCS2, etc. This coding scheme
525      *                      needs to match those used passed to HAL from the native GPS driver. Decoding is done according
526      *                   to the <code> coding </code>, after a Hex string is decoded. Generally, if the
527      *                   notification strings don't need further decoding, <code> coding </code> encoding can be
528      *                   set to -1, and <code> isHex </code> can be false.
529      * @return the decoded string
530      */
531     static private String decodeString(String original, boolean isHex, int coding)
532     {
533         String decoded = original;
534         byte[] input = stringToByteArray(original, isHex);
535
536         switch (coding) {
537         case GPS_ENC_NONE:
538             decoded = original;
539             break;
540
541         case GPS_ENC_SUPL_GSM_DEFAULT:
542             decoded = decodeGSMPackedString(input);
543             break;
544
545         case GPS_ENC_SUPL_UTF8:
546             decoded = decodeUTF8String(input);
547             break;
548
549         case GPS_ENC_SUPL_UCS2:
550             decoded = decodeUCS2String(input);
551             break;
552
553         case GPS_ENC_UNKNOWN:
554             decoded = original;
555             break;
556
557         default:
558             Log.e(TAG, "Unknown encoding " + coding + " for NI text " + original);
559             break;
560         }
561         return decoded;
562     }
563
564     // change this to configure notification display
565     static private String getNotifTicker(GpsNiNotification notif, Context context)
566     {
567         String ticker = String.format(context.getString(R.string.gpsNotifTicker),
568                 decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding),
569                 decodeString(notif.text, mIsHexInput, notif.textEncoding));
570         return ticker;
571     }
572
573     // change this to configure notification display
574     static private String getNotifTitle(GpsNiNotification notif, Context context)
575     {
576         String title = String.format(context.getString(R.string.gpsNotifTitle));
577         return title;
578     }
579
580     // change this to configure notification display
581     static private String getNotifMessage(GpsNiNotification notif, Context context)
582     {
583         String message = String.format(context.getString(R.string.gpsNotifMessage),
584                 decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding),
585                 decodeString(notif.text, mIsHexInput, notif.textEncoding));
586         return message;
587     }
588
589     // change this to configure dialog display (for verification)
590     static public String getDialogTitle(GpsNiNotification notif, Context context)
591     {
592         return getNotifTitle(notif, context);
593     }
594
595     // change this to configure dialog display (for verification)
596     static private String getDialogMessage(GpsNiNotification notif, Context context)
597     {
598         return getNotifMessage(notif, context);
599     }
600
601 }