2 * Copyright (C) 2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.internal.location;
19 import java.io.UnsupportedEncodingException;
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;
39 import com.android.internal.R;
40 import com.android.internal.telephony.GsmAlphabet;
41 import com.android.internal.telephony.TelephonyProperties;
44 * A GPS Network-initiated Handler class used by LocationManager.
48 public class GpsNetInitiatedHandler {
50 private static final String TAG = "GpsNetInitiatedHandler";
52 private static final boolean DEBUG = true;
53 private static final boolean VERBOSE = false;
55 // NI verify activity for bringing up UI (not used yet)
56 public static final String ACTION_NI_VERIFY = "android.intent.action.NETWORK_INITIATED_VERIFY";
58 // string constants for defining data fields in NI Intent
59 public static final String NI_INTENT_KEY_NOTIF_ID = "notif_id";
60 public static final String NI_INTENT_KEY_TITLE = "title";
61 public static final String NI_INTENT_KEY_MESSAGE = "message";
62 public static final String NI_INTENT_KEY_TIMEOUT = "timeout";
63 public static final String NI_INTENT_KEY_DEFAULT_RESPONSE = "default_resp";
65 // the extra command to send NI response to GnssLocationProvider
66 public static final String NI_RESPONSE_EXTRA_CMD = "send_ni_response";
68 // the extra command parameter names in the Bundle
69 public static final String NI_EXTRA_CMD_NOTIF_ID = "notif_id";
70 public static final String NI_EXTRA_CMD_RESPONSE = "response";
72 // these need to match GpsNiType constants in gps_ni.h
73 public static final int GPS_NI_TYPE_VOICE = 1;
74 public static final int GPS_NI_TYPE_UMTS_SUPL = 2;
75 public static final int GPS_NI_TYPE_UMTS_CTRL_PLANE = 3;
76 public static final int GPS_NI_TYPE_EMERGENCY_SUPL = 4;
78 // these need to match GpsUserResponseType constants in gps_ni.h
79 public static final int GPS_NI_RESPONSE_ACCEPT = 1;
80 public static final int GPS_NI_RESPONSE_DENY = 2;
81 public static final int GPS_NI_RESPONSE_NORESP = 3;
82 public static final int GPS_NI_RESPONSE_IGNORE = 4;
84 // these need to match GpsNiNotifyFlags constants in gps_ni.h
85 public static final int GPS_NI_NEED_NOTIFY = 0x0001;
86 public static final int GPS_NI_NEED_VERIFY = 0x0002;
87 public static final int GPS_NI_PRIVACY_OVERRIDE = 0x0004;
89 // these need to match GpsNiEncodingType in gps_ni.h
90 public static final int GPS_ENC_NONE = 0;
91 public static final int GPS_ENC_SUPL_GSM_DEFAULT = 1;
92 public static final int GPS_ENC_SUPL_UTF8 = 2;
93 public static final int GPS_ENC_SUPL_UCS2 = 3;
94 public static final int GPS_ENC_UNKNOWN = -1;
96 private final Context mContext;
97 private final TelephonyManager mTelephonyManager;
98 private final PhoneStateListener mPhoneStateListener;
100 // parent gps location provider
101 private final LocationManager mLocationManager;
103 // configuration of notificaiton behavior
104 private boolean mPlaySounds = false;
105 private boolean mPopupImmediately = true;
107 // read the SUPL_ES form gps.conf
108 private volatile boolean mIsSuplEsEnabled;
110 // Set to true if the phone is having emergency call.
111 private volatile boolean mIsInEmergency;
113 // If Location function is enabled.
114 private volatile boolean mIsLocationEnabled = false;
116 private final INetInitiatedListener mNetInitiatedListener;
118 // Set to true if string from HAL is encoded as Hex, e.g., "3F0039"
119 static private boolean mIsHexInput = true;
121 public static class GpsNiNotification
123 public int notificationId;
125 public boolean needNotify;
126 public boolean needVerify;
127 public boolean privacyOverride;
129 public int defaultResponse;
130 public String requestorId;
132 public int requestorIdEncoding;
133 public int textEncoding;
134 public Bundle extras;
137 public static class GpsNiResponse {
138 /* User response, one of the values in GpsUserResponseType */
140 /* Optional extra data to pass with the user response */
144 private final BroadcastReceiver mBroadcastReciever = new BroadcastReceiver() {
146 @Override public void onReceive(Context context, Intent intent) {
147 String action = intent.getAction();
148 if (action.equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
149 String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
151 Emergency Mode is when during emergency call or in emergency call back mode.
152 For checking if it is during emergency call:
153 mIsInEmergency records if the phone is in emergency call or not. It will
154 be set to true when the phone is having emergency call, and then will
155 be set to false by mPhoneStateListener when the emergency call ends.
156 For checking if it is in emergency call back mode:
157 Emergency call back mode will be checked by reading system properties
158 when necessary: SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE)
160 setInEmergency(PhoneNumberUtils.isEmergencyNumber(phoneNumber));
161 if (DEBUG) Log.v(TAG, "ACTION_NEW_OUTGOING_CALL - " + getInEmergency());
162 } else if (action.equals(LocationManager.MODE_CHANGED_ACTION)) {
163 updateLocationMode();
164 if (DEBUG) Log.d(TAG, "location enabled :" + getLocationEnabled());
170 * The notification that is shown when a network-initiated notification
171 * (and verification) event is received.
173 * This is lazily created, so use {@link #setNINotification()}.
175 private Notification.Builder mNiNotificationBuilder;
177 public GpsNetInitiatedHandler(Context context,
178 INetInitiatedListener netInitiatedListener,
179 boolean isSuplEsEnabled) {
182 if (netInitiatedListener == null) {
183 throw new IllegalArgumentException("netInitiatedListener is null");
185 mNetInitiatedListener = netInitiatedListener;
188 setSuplEsEnabled(isSuplEsEnabled);
189 mLocationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE);
190 updateLocationMode();
192 (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
194 mPhoneStateListener = new PhoneStateListener() {
196 public void onCallStateChanged(int state, String incomingNumber) {
197 if (DEBUG) Log.d(TAG, "onCallStateChanged(): state is "+ state);
198 // listening for emergency call ends
199 if (state == TelephonyManager.CALL_STATE_IDLE) {
200 setInEmergency(false);
204 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
206 IntentFilter intentFilter = new IntentFilter();
207 intentFilter.addAction(Intent.ACTION_NEW_OUTGOING_CALL);
208 intentFilter.addAction(LocationManager.MODE_CHANGED_ACTION);
209 mContext.registerReceiver(mBroadcastReciever, intentFilter);
212 public void setSuplEsEnabled(boolean isEnabled) {
213 mIsSuplEsEnabled = isEnabled;
216 public boolean getSuplEsEnabled() {
217 return mIsSuplEsEnabled;
221 * Updates Location enabler based on location setting.
223 public void updateLocationMode() {
224 mIsLocationEnabled = mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
228 * Checks if user agreed to use location.
230 public boolean getLocationEnabled() {
231 return mIsLocationEnabled;
234 // Note: Currently, there are two mechanisms involved to determine if a
235 // phone is in emergency mode:
236 // 1. If the user is making an emergency call, this is provided by activly
237 // monitoring the outgoing phone number;
238 // 2. If the device is in a emergency callback state, this is provided by
239 // system properties.
240 // If either one of above exists, the phone is considered in an emergency
241 // mode. Because of this complexity, we need to be careful about how to set
242 // and clear the emergency state.
243 public void setInEmergency(boolean isInEmergency) {
244 mIsInEmergency = isInEmergency;
247 public boolean getInEmergency() {
248 boolean isInEmergencyCallback = Boolean.parseBoolean(
249 SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE));
250 return mIsInEmergency || isInEmergencyCallback;
254 // Handles NI events from HAL
255 public void handleNiNotification(GpsNiNotification notif) {
256 if (DEBUG) Log.d(TAG, "in handleNiNotification () :"
257 + " notificationId: " + notif.notificationId
258 + " requestorId: " + notif.requestorId
259 + " text: " + notif.text
260 + " mIsSuplEsEnabled" + getSuplEsEnabled()
261 + " mIsLocationEnabled" + getLocationEnabled());
263 if (getSuplEsEnabled()) {
269 //////////////////////////////////////////////////////////////////////////
270 // A note about timeout
271 // According to the protocol, in the need_notify and need_verify case,
272 // a default response should be sent when time out.
274 // In some GPS hardware, the GPS driver (under HAL) can handle the timeout case
275 // and this class GpsNetInitiatedHandler does not need to do anything.
277 // However, the UI should at least close the dialog when timeout. Further,
278 // for more general handling, timeout response should be added to the Handler here.
282 // handle NI form HAL when SUPL_ES is disabled.
283 private void handleNi(GpsNiNotification notif) {
284 if (DEBUG) Log.d(TAG, "in handleNi () :"
285 + " needNotify: " + notif.needNotify
286 + " needVerify: " + notif.needVerify
287 + " privacyOverride: " + notif.privacyOverride
288 + " mPopupImmediately: " + mPopupImmediately
289 + " mInEmergency: " + getInEmergency());
291 if (!getLocationEnabled() && !getInEmergency()) {
292 // Location is currently disabled, ignore all NI requests.
294 mNetInitiatedListener.sendNiResponse(notif.notificationId,
295 GPS_NI_RESPONSE_IGNORE);
296 } catch (RemoteException e) {
297 Log.e(TAG, "RemoteException in sendNiResponse");
300 if (notif.needNotify) {
301 // If NI does not need verify or the dialog is not requested
302 // to pop up immediately, the dialog box will not pop up.
303 if (notif.needVerify && mPopupImmediately) {
304 // Popup the dialog box now
307 // Show the notification
308 setNiNotification(notif);
311 // ACCEPT cases: 1. Notify, no verify; 2. no notify, no verify;
312 // 3. privacy override.
313 if (!notif.needVerify || notif.privacyOverride) {
315 mNetInitiatedListener.sendNiResponse(notif.notificationId,
316 GPS_NI_RESPONSE_ACCEPT);
317 } catch (RemoteException e) {
318 Log.e(TAG, "RemoteException in sendNiResponse");
323 // handle NI from HAL when the SUPL_ES is enabled
324 private void handleNiInEs(GpsNiNotification notif) {
326 if (DEBUG) Log.d(TAG, "in handleNiInEs () :"
327 + " niType: " + notif.niType
328 + " notificationId: " + notif.notificationId);
330 // UE is in emergency mode when in emergency call mode or in emergency call back mode
332 1. When SUPL ES bit is off and UE is not in emergency mode:
333 Call handleNi() to do legacy behaviour.
334 2. When SUPL ES bit is on and UE is in emergency mode:
335 Call handleNi() to do acceptance behaviour.
336 3. When SUPL ES bit is off but UE is in emergency mode:
337 Ignore the emergency SUPL INIT.
338 4. When SUPL ES bit is on but UE is not in emergency mode:
339 Ignore the emergency SUPL INIT.
341 boolean isNiTypeES = (notif.niType == GPS_NI_TYPE_EMERGENCY_SUPL);
342 if (isNiTypeES != getInEmergency()) {
344 mNetInitiatedListener.sendNiResponse(notif.notificationId,
345 GPS_NI_RESPONSE_IGNORE);
346 } catch (RemoteException e) {
347 Log.e(TAG, "RemoteException in sendNiResponse");
354 // Sets the NI notification.
355 private synchronized void setNiNotification(GpsNiNotification notif) {
356 NotificationManager notificationManager = (NotificationManager) mContext
357 .getSystemService(Context.NOTIFICATION_SERVICE);
358 if (notificationManager == null) {
362 String title = getNotifTitle(notif, mContext);
363 String message = getNotifMessage(notif, mContext);
365 if (DEBUG) Log.d(TAG, "setNiNotification, notifyId: " + notif.notificationId +
366 ", title: " + title +
367 ", message: " + message);
369 // Construct Notification
370 if (mNiNotificationBuilder == null) {
371 mNiNotificationBuilder = new Notification.Builder(mContext)
372 .setSmallIcon(com.android.internal.R.drawable.stat_sys_gps_on)
376 .setColor(mContext.getColor(
377 com.android.internal.R.color.system_notification_accent_color));
381 mNiNotificationBuilder.setDefaults(Notification.DEFAULT_SOUND);
383 mNiNotificationBuilder.setDefaults(0);
386 // if not to popup dialog immediately, pending intent will open the dialog
387 Intent intent = !mPopupImmediately ? getDlgIntent(notif) : new Intent();
388 PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
389 mNiNotificationBuilder.setTicker(getNotifTicker(notif, mContext))
390 .setContentTitle(title)
391 .setContentText(message)
392 .setContentIntent(pi);
394 notificationManager.notifyAsUser(null, notif.notificationId, mNiNotificationBuilder.build(),
398 // Opens the notification dialog and waits for user input
399 private void openNiDialog(GpsNiNotification notif)
401 Intent intent = getDlgIntent(notif);
403 if (DEBUG) Log.d(TAG, "openNiDialog, notifyId: " + notif.notificationId +
404 ", requestorId: " + notif.requestorId +
405 ", text: " + notif.text);
407 mContext.startActivity(intent);
410 // Construct the intent for bringing up the dialog activity, which shows the
411 // notification and takes user input
412 private Intent getDlgIntent(GpsNiNotification notif)
414 Intent intent = new Intent();
415 String title = getDialogTitle(notif, mContext);
416 String message = getDialogMessage(notif, mContext);
418 // directly bring up the NI activity
419 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
420 intent.setClass(mContext, com.android.internal.app.NetInitiatedActivity.class);
422 // put data in the intent
423 intent.putExtra(NI_INTENT_KEY_NOTIF_ID, notif.notificationId);
424 intent.putExtra(NI_INTENT_KEY_TITLE, title);
425 intent.putExtra(NI_INTENT_KEY_MESSAGE, message);
426 intent.putExtra(NI_INTENT_KEY_TIMEOUT, notif.timeout);
427 intent.putExtra(NI_INTENT_KEY_DEFAULT_RESPONSE, notif.defaultResponse);
429 if (DEBUG) Log.d(TAG, "generateIntent, title: " + title + ", message: " + message +
430 ", timeout: " + notif.timeout);
435 // Converts a string (or Hex string) to a char array
436 static byte[] stringToByteArray(String original, boolean isHex)
438 int length = isHex ? original.length() / 2 : original.length();
439 byte[] output = new byte[length];
444 for (i = 0; i < length; i++)
446 output[i] = (byte) Integer.parseInt(original.substring(i*2, i*2+2), 16);
450 for (i = 0; i < length; i++)
452 output[i] = (byte) original.charAt(i);
460 * Unpacks an byte array containing 7-bit packed characters into a String.
462 * @param input a 7-bit packed char array
463 * @return the unpacked String
465 static String decodeGSMPackedString(byte[] input)
467 final char PADDING_CHAR = 0x00;
468 int lengthBytes = input.length;
469 int lengthSeptets = (lengthBytes * 8) / 7;
472 /* Special case where the last 7 bits in the last byte could hold a valid
473 * 7-bit character or a padding character. Drop the last 7-bit character
474 * if it is a padding character.
476 if (lengthBytes % 7 == 0) {
477 if (lengthBytes > 0) {
478 if ((input[lengthBytes - 1] >> 1) == PADDING_CHAR) {
479 lengthSeptets = lengthSeptets - 1;
484 decoded = GsmAlphabet.gsm7BitPackedToString(input, 0, lengthSeptets);
486 // Return "" if decoding of GSM packed string fails
487 if (null == decoded) {
488 Log.e(TAG, "Decoding of GSM packed string failed");
495 static String decodeUTF8String(byte[] input)
499 decoded = new String(input, "UTF-8");
501 catch (UnsupportedEncodingException e)
503 throw new AssertionError();
508 static String decodeUCS2String(byte[] input)
512 decoded = new String(input, "UTF-16");
514 catch (UnsupportedEncodingException e)
516 throw new AssertionError();
523 * @param original The text string to be decoded
524 * @param isHex Specifies whether the content of the string has been encoded as a Hex string. Encoding
525 * a string as Hex can allow zeros inside the coded text.
526 * @param coding Specifies the coding scheme of the string, such as GSM, UTF8, UCS2, etc. This coding scheme
527 * needs to match those used passed to HAL from the native GPS driver. Decoding is done according
528 * to the <code> coding </code>, after a Hex string is decoded. Generally, if the
529 * notification strings don't need further decoding, <code> coding </code> encoding can be
530 * set to -1, and <code> isHex </code> can be false.
531 * @return the decoded string
533 static private String decodeString(String original, boolean isHex, int coding)
535 String decoded = original;
536 byte[] input = stringToByteArray(original, isHex);
543 case GPS_ENC_SUPL_GSM_DEFAULT:
544 decoded = decodeGSMPackedString(input);
547 case GPS_ENC_SUPL_UTF8:
548 decoded = decodeUTF8String(input);
551 case GPS_ENC_SUPL_UCS2:
552 decoded = decodeUCS2String(input);
555 case GPS_ENC_UNKNOWN:
560 Log.e(TAG, "Unknown encoding " + coding + " for NI text " + original);
566 // change this to configure notification display
567 static private String getNotifTicker(GpsNiNotification notif, Context context)
569 String ticker = String.format(context.getString(R.string.gpsNotifTicker),
570 decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding),
571 decodeString(notif.text, mIsHexInput, notif.textEncoding));
575 // change this to configure notification display
576 static private String getNotifTitle(GpsNiNotification notif, Context context)
578 String title = String.format(context.getString(R.string.gpsNotifTitle));
582 // change this to configure notification display
583 static private String getNotifMessage(GpsNiNotification notif, Context context)
585 String message = String.format(context.getString(R.string.gpsNotifMessage),
586 decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding),
587 decodeString(notif.text, mIsHexInput, notif.textEncoding));
591 // change this to configure dialog display (for verification)
592 static public String getDialogTitle(GpsNiNotification notif, Context context)
594 return getNotifTitle(notif, context);
597 // change this to configure dialog display (for verification)
598 static private String getDialogMessage(GpsNiNotification notif, Context context)
600 return getNotifMessage(notif, context);