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.notification.SystemNotificationChannels;
40 import com.android.internal.R;
41 import com.android.internal.telephony.GsmAlphabet;
42 import com.android.internal.telephony.TelephonyProperties;
45 * A GPS Network-initiated Handler class used by LocationManager.
49 public class GpsNetInitiatedHandler {
51 private static final String TAG = "GpsNetInitiatedHandler";
53 private static final boolean DEBUG = true;
54 private static final boolean VERBOSE = false;
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";
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";
66 // the extra command to send NI response to GnssLocationProvider
67 public static final String NI_RESPONSE_EXTRA_CMD = "send_ni_response";
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";
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;
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;
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;
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;
97 private final Context mContext;
98 private final TelephonyManager mTelephonyManager;
99 private final PhoneStateListener mPhoneStateListener;
101 // parent gps location provider
102 private final LocationManager mLocationManager;
104 // configuration of notificaiton behavior
105 private boolean mPlaySounds = false;
106 private boolean mPopupImmediately = true;
108 // read the SUPL_ES form gps.conf
109 private volatile boolean mIsSuplEsEnabled;
111 // Set to true if the phone is having emergency call.
112 private volatile boolean mIsInEmergency;
114 // If Location function is enabled.
115 private volatile boolean mIsLocationEnabled = false;
117 private final INetInitiatedListener mNetInitiatedListener;
119 // Set to true if string from HAL is encoded as Hex, e.g., "3F0039"
120 static private boolean mIsHexInput = true;
122 public static class GpsNiNotification
124 public int notificationId;
126 public boolean needNotify;
127 public boolean needVerify;
128 public boolean privacyOverride;
130 public int defaultResponse;
131 public String requestorId;
133 public int requestorIdEncoding;
134 public int textEncoding;
137 public static class GpsNiResponse {
138 /* User response, one of the values in GpsUserResponseType */
142 private final BroadcastReceiver mBroadcastReciever = new BroadcastReceiver() {
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);
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)
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());
168 * The notification that is shown when a network-initiated notification
169 * (and verification) event is received.
171 * This is lazily created, so use {@link #setNINotification()}.
173 private Notification.Builder mNiNotificationBuilder;
175 public GpsNetInitiatedHandler(Context context,
176 INetInitiatedListener netInitiatedListener,
177 boolean isSuplEsEnabled) {
180 if (netInitiatedListener == null) {
181 throw new IllegalArgumentException("netInitiatedListener is null");
183 mNetInitiatedListener = netInitiatedListener;
186 setSuplEsEnabled(isSuplEsEnabled);
187 mLocationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE);
188 updateLocationMode();
190 (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
192 mPhoneStateListener = new PhoneStateListener() {
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);
202 mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
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);
210 public void setSuplEsEnabled(boolean isEnabled) {
211 mIsSuplEsEnabled = isEnabled;
214 public boolean getSuplEsEnabled() {
215 return mIsSuplEsEnabled;
219 * Updates Location enabler based on location setting.
221 public void updateLocationMode() {
222 mIsLocationEnabled = mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
226 * Checks if user agreed to use location.
228 public boolean getLocationEnabled() {
229 return mIsLocationEnabled;
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;
245 public boolean getInEmergency() {
246 boolean isInEmergencyCallback = mTelephonyManager.getEmergencyCallbackMode();
247 return mIsInEmergency || isInEmergencyCallback;
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());
260 if (getSuplEsEnabled()) {
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.
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.
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.
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());
288 if (!getLocationEnabled() && !getInEmergency()) {
289 // Location is currently disabled, ignore all NI requests.
291 mNetInitiatedListener.sendNiResponse(notif.notificationId,
292 GPS_NI_RESPONSE_IGNORE);
293 } catch (RemoteException e) {
294 Log.e(TAG, "RemoteException in sendNiResponse");
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
304 // Show the notification
305 setNiNotification(notif);
308 // ACCEPT cases: 1. Notify, no verify; 2. no notify, no verify;
309 // 3. privacy override.
310 if (!notif.needVerify || notif.privacyOverride) {
312 mNetInitiatedListener.sendNiResponse(notif.notificationId,
313 GPS_NI_RESPONSE_ACCEPT);
314 } catch (RemoteException e) {
315 Log.e(TAG, "RemoteException in sendNiResponse");
320 // handle NI from HAL when the SUPL_ES is enabled
321 private void handleNiInEs(GpsNiNotification notif) {
323 if (DEBUG) Log.d(TAG, "in handleNiInEs () :"
324 + " niType: " + notif.niType
325 + " notificationId: " + notif.notificationId);
327 // UE is in emergency mode when in emergency call mode or in emergency call back mode
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.
338 boolean isNiTypeES = (notif.niType == GPS_NI_TYPE_EMERGENCY_SUPL);
339 if (isNiTypeES != getInEmergency()) {
341 mNetInitiatedListener.sendNiResponse(notif.notificationId,
342 GPS_NI_RESPONSE_IGNORE);
343 } catch (RemoteException e) {
344 Log.e(TAG, "RemoteException in sendNiResponse");
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) {
359 String title = getNotifTitle(notif, mContext);
360 String message = getNotifMessage(notif, mContext);
362 if (DEBUG) Log.d(TAG, "setNiNotification, notifyId: " + notif.notificationId +
363 ", title: " + title +
364 ", message: " + message);
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)
374 .setColor(mContext.getColor(
375 com.android.internal.R.color.system_notification_accent_color));
379 mNiNotificationBuilder.setDefaults(Notification.DEFAULT_SOUND);
381 mNiNotificationBuilder.setDefaults(0);
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);
392 notificationManager.notifyAsUser(null, notif.notificationId, mNiNotificationBuilder.build(),
396 // Opens the notification dialog and waits for user input
397 private void openNiDialog(GpsNiNotification notif)
399 Intent intent = getDlgIntent(notif);
401 if (DEBUG) Log.d(TAG, "openNiDialog, notifyId: " + notif.notificationId +
402 ", requestorId: " + notif.requestorId +
403 ", text: " + notif.text);
405 mContext.startActivity(intent);
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)
412 Intent intent = new Intent();
413 String title = getDialogTitle(notif, mContext);
414 String message = getDialogMessage(notif, mContext);
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);
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);
427 if (DEBUG) Log.d(TAG, "generateIntent, title: " + title + ", message: " + message +
428 ", timeout: " + notif.timeout);
433 // Converts a string (or Hex string) to a char array
434 static byte[] stringToByteArray(String original, boolean isHex)
436 int length = isHex ? original.length() / 2 : original.length();
437 byte[] output = new byte[length];
442 for (i = 0; i < length; i++)
444 output[i] = (byte) Integer.parseInt(original.substring(i*2, i*2+2), 16);
448 for (i = 0; i < length; i++)
450 output[i] = (byte) original.charAt(i);
458 * Unpacks an byte array containing 7-bit packed characters into a String.
460 * @param input a 7-bit packed char array
461 * @return the unpacked String
463 static String decodeGSMPackedString(byte[] input)
465 final char PADDING_CHAR = 0x00;
466 int lengthBytes = input.length;
467 int lengthSeptets = (lengthBytes * 8) / 7;
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.
474 if (lengthBytes % 7 == 0) {
475 if (lengthBytes > 0) {
476 if ((input[lengthBytes - 1] >> 1) == PADDING_CHAR) {
477 lengthSeptets = lengthSeptets - 1;
482 decoded = GsmAlphabet.gsm7BitPackedToString(input, 0, lengthSeptets);
484 // Return "" if decoding of GSM packed string fails
485 if (null == decoded) {
486 Log.e(TAG, "Decoding of GSM packed string failed");
493 static String decodeUTF8String(byte[] input)
497 decoded = new String(input, "UTF-8");
499 catch (UnsupportedEncodingException e)
501 throw new AssertionError();
506 static String decodeUCS2String(byte[] input)
510 decoded = new String(input, "UTF-16");
512 catch (UnsupportedEncodingException e)
514 throw new AssertionError();
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
531 static private String decodeString(String original, boolean isHex, int coding)
533 String decoded = original;
534 byte[] input = stringToByteArray(original, isHex);
541 case GPS_ENC_SUPL_GSM_DEFAULT:
542 decoded = decodeGSMPackedString(input);
545 case GPS_ENC_SUPL_UTF8:
546 decoded = decodeUTF8String(input);
549 case GPS_ENC_SUPL_UCS2:
550 decoded = decodeUCS2String(input);
553 case GPS_ENC_UNKNOWN:
558 Log.e(TAG, "Unknown encoding " + coding + " for NI text " + original);
564 // change this to configure notification display
565 static private String getNotifTicker(GpsNiNotification notif, Context context)
567 String ticker = String.format(context.getString(R.string.gpsNotifTicker),
568 decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding),
569 decodeString(notif.text, mIsHexInput, notif.textEncoding));
573 // change this to configure notification display
574 static private String getNotifTitle(GpsNiNotification notif, Context context)
576 String title = String.format(context.getString(R.string.gpsNotifTitle));
580 // change this to configure notification display
581 static private String getNotifMessage(GpsNiNotification notif, Context context)
583 String message = String.format(context.getString(R.string.gpsNotifMessage),
584 decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding),
585 decodeString(notif.text, mIsHexInput, notif.textEncoding));
589 // change this to configure dialog display (for verification)
590 static public String getDialogTitle(GpsNiNotification notif, Context context)
592 return getNotifTitle(notif, context);
595 // change this to configure dialog display (for verification)
596 static private String getDialogMessage(GpsNiNotification notif, Context context)
598 return getNotifMessage(notif, context);