OSDN Git Service

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