package com.android.internal.location;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.TimeUnit;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.LocationManager;
import android.location.INetInitiatedListener;
import android.os.SystemClock;
import android.telephony.TelephonyManager;
import android.telephony.PhoneNumberUtils;
import android.telephony.PhoneStateListener;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.R;
import com.android.internal.telephony.GsmAlphabet;
public class GpsNetInitiatedHandler {
private static final String TAG = "GpsNetInitiatedHandler";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
public static final String ACTION_NI_VERIFY = "android.intent.action.NETWORK_INITIATED_VERIFY";
public static final String NI_INTENT_KEY_NOTIF_ID = "notif_id";
public static final String NI_INTENT_KEY_TITLE = "title";
public static final String NI_INTENT_KEY_MESSAGE = "message";
public static final String NI_INTENT_KEY_TIMEOUT = "timeout";
public static final String NI_INTENT_KEY_DEFAULT_RESPONSE = "default_resp";
public static final String NI_RESPONSE_EXTRA_CMD = "send_ni_response";
public static final String NI_EXTRA_CMD_NOTIF_ID = "notif_id";
public static final String NI_EXTRA_CMD_RESPONSE = "response";
public static final int GPS_NI_TYPE_VOICE = 1;
public static final int GPS_NI_TYPE_UMTS_SUPL = 2;
public static final int GPS_NI_TYPE_UMTS_CTRL_PLANE = 3;
public static final int GPS_NI_TYPE_EMERGENCY_SUPL = 4;
public static final int GPS_NI_RESPONSE_ACCEPT = 1;
public static final int GPS_NI_RESPONSE_DENY = 2;
public static final int GPS_NI_RESPONSE_NORESP = 3;
public static final int GPS_NI_RESPONSE_IGNORE = 4;
public static final int GPS_NI_NEED_NOTIFY = 0x0001;
public static final int GPS_NI_NEED_VERIFY = 0x0002;
public static final int GPS_NI_PRIVACY_OVERRIDE = 0x0004;
public static final int GPS_ENC_NONE = 0;
public static final int GPS_ENC_SUPL_GSM_DEFAULT = 1;
public static final int GPS_ENC_SUPL_UTF8 = 2;
public static final int GPS_ENC_SUPL_UCS2 = 3;
public static final int GPS_ENC_UNKNOWN = -1;
private static final int MAX_EMERGENCY_MODE_EXTENSION_SECONDS = 300;
private final Context mContext;
private final TelephonyManager mTelephonyManager;
private final PhoneStateListener mPhoneStateListener;
private final LocationManager mLocationManager;
private boolean mPlaySounds = false;
private boolean mPopupImmediately = true;
private volatile boolean mIsSuplEsEnabled;
private volatile boolean mIsInEmergencyCall;
private volatile boolean mIsLocationEnabled = false;
private final INetInitiatedListener mNetInitiatedListener;
static private boolean mIsHexInput = true;
private long mCallEndElapsedRealtimeMillis = 0;
private long mEmergencyExtensionMillis = 0;
public static class GpsNiNotification
{
public int notificationId;
public int niType;
public boolean needNotify;
public boolean needVerify;
public boolean privacyOverride;
public int timeout;
public int defaultResponse;
public String requestorId;
public String text;
public int requestorIdEncoding;
public int textEncoding;
};
public static class GpsNiResponse {
int userResponse;
};
private final BroadcastReceiver mBroadcastReciever = new BroadcastReceiver() {
@Override public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
mIsInEmergencyCall = PhoneNumberUtils.isEmergencyNumber(phoneNumber);
if (DEBUG) Log.v(TAG, "ACTION_NEW_OUTGOING_CALL - " + getInEmergency());
} else if (action.equals(LocationManager.MODE_CHANGED_ACTION)) {
updateLocationMode();
if (DEBUG) Log.d(TAG, "location enabled :" + getLocationEnabled());
}
}
};
private Notification.Builder mNiNotificationBuilder;
public GpsNetInitiatedHandler(Context context,
INetInitiatedListener netInitiatedListener,
boolean isSuplEsEnabled) {
mContext = context;
if (netInitiatedListener == null) {
throw new IllegalArgumentException("netInitiatedListener is null");
} else {
mNetInitiatedListener = netInitiatedListener;
}
setSuplEsEnabled(isSuplEsEnabled);
mLocationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE);
updateLocationMode();
mTelephonyManager =
(TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
mPhoneStateListener = new PhoneStateListener() {
@Override
public void onCallStateChanged(int state, String incomingNumber) {
if (DEBUG) Log.d(TAG, "onCallStateChanged(): state is "+ state);
if (state == TelephonyManager.CALL_STATE_IDLE) {
if (mIsInEmergencyCall) {
mCallEndElapsedRealtimeMillis = SystemClock.elapsedRealtime();
mIsInEmergencyCall = false;
}
}
}
};
mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_NEW_OUTGOING_CALL);
intentFilter.addAction(LocationManager.MODE_CHANGED_ACTION);
mContext.registerReceiver(mBroadcastReciever, intentFilter);
}
public void setSuplEsEnabled(boolean isEnabled) {
mIsSuplEsEnabled = isEnabled;
}
public boolean getSuplEsEnabled() {
return mIsSuplEsEnabled;
}
public void updateLocationMode() {
mIsLocationEnabled = mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
}
public boolean getLocationEnabled() {
return mIsLocationEnabled;
}
public boolean getInEmergency() {
boolean isInEmergencyExtension =
(SystemClock.elapsedRealtime() - mCallEndElapsedRealtimeMillis) <
mEmergencyExtensionMillis;
boolean isInEmergencyCallback = mTelephonyManager.getEmergencyCallbackMode();
return mIsInEmergencyCall || isInEmergencyCallback || isInEmergencyExtension;
}
public void setEmergencyExtensionSeconds(int emergencyExtensionSeconds) {
if (emergencyExtensionSeconds > MAX_EMERGENCY_MODE_EXTENSION_SECONDS) {
Log.w(TAG, "emergencyExtensionSeconds " + emergencyExtensionSeconds
+ " too high, reset to " + MAX_EMERGENCY_MODE_EXTENSION_SECONDS);
emergencyExtensionSeconds = MAX_EMERGENCY_MODE_EXTENSION_SECONDS;
} else if (emergencyExtensionSeconds < 0) {
Log.w(TAG, "emergencyExtensionSeconds " + emergencyExtensionSeconds
+ " is negative, reset to zero.");
emergencyExtensionSeconds = 0;
}
mEmergencyExtensionMillis = TimeUnit.SECONDS.toMillis(emergencyExtensionSeconds);
}
public void handleNiNotification(GpsNiNotification notif) {
if (DEBUG) Log.d(TAG, "in handleNiNotification () :"
+ " notificationId: " + notif.notificationId
+ " requestorId: " + notif.requestorId
+ " text: " + notif.text
+ " mIsSuplEsEnabled" + getSuplEsEnabled()
+ " mIsLocationEnabled" + getLocationEnabled());
if (getSuplEsEnabled()) {
handleNiInEs(notif);
} else {
handleNi(notif);
}
}
private void handleNi(GpsNiNotification notif) {
if (DEBUG) Log.d(TAG, "in handleNi () :"
+ " needNotify: " + notif.needNotify
+ " needVerify: " + notif.needVerify
+ " privacyOverride: " + notif.privacyOverride
+ " mPopupImmediately: " + mPopupImmediately
+ " mInEmergency: " + getInEmergency());
if (!getLocationEnabled() && !getInEmergency()) {
try {
mNetInitiatedListener.sendNiResponse(notif.notificationId,
GPS_NI_RESPONSE_IGNORE);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in sendNiResponse");
}
}
if (notif.needNotify) {
if (notif.needVerify && mPopupImmediately) {
openNiDialog(notif);
} else {
setNiNotification(notif);
}
}
if (!notif.needVerify || notif.privacyOverride) {
try {
mNetInitiatedListener.sendNiResponse(notif.notificationId,
GPS_NI_RESPONSE_ACCEPT);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in sendNiResponse");
}
}
}
private void handleNiInEs(GpsNiNotification notif) {
if (DEBUG) Log.d(TAG, "in handleNiInEs () :"
+ " niType: " + notif.niType
+ " notificationId: " + notif.notificationId);
boolean isNiTypeES = (notif.niType == GPS_NI_TYPE_EMERGENCY_SUPL);
if (isNiTypeES != getInEmergency()) {
try {
mNetInitiatedListener.sendNiResponse(notif.notificationId,
GPS_NI_RESPONSE_IGNORE);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in sendNiResponse");
}
} else {
handleNi(notif);
}
}
private synchronized void setNiNotification(GpsNiNotification notif) {
NotificationManager notificationManager = (NotificationManager) mContext
.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager == null) {
return;
}
String title = getNotifTitle(notif, mContext);
String message = getNotifMessage(notif, mContext);
if (DEBUG) Log.d(TAG, "setNiNotification, notifyId: " + notif.notificationId +
", title: " + title +
", message: " + message);
if (mNiNotificationBuilder == null) {
mNiNotificationBuilder = new Notification.Builder(mContext,
SystemNotificationChannels.NETWORK_ALERTS)
.setSmallIcon(com.android.internal.R.drawable.stat_sys_gps_on)
.setWhen(0)
.setOngoing(true)
.setAutoCancel(true)
.setColor(mContext.getColor(
com.android.internal.R.color.system_notification_accent_color));
}
if (mPlaySounds) {
mNiNotificationBuilder.setDefaults(Notification.DEFAULT_SOUND);
} else {
mNiNotificationBuilder.setDefaults(0);
}
Intent intent = !mPopupImmediately ? getDlgIntent(notif) : new Intent();
PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
mNiNotificationBuilder.setTicker(getNotifTicker(notif, mContext))
.setContentTitle(title)
.setContentText(message)
.setContentIntent(pi);
notificationManager.notifyAsUser(null, notif.notificationId, mNiNotificationBuilder.build(),
UserHandle.ALL);
}
private void openNiDialog(GpsNiNotification notif)
{
Intent intent = getDlgIntent(notif);
if (DEBUG) Log.d(TAG, "openNiDialog, notifyId: " + notif.notificationId +
", requestorId: " + notif.requestorId +
", text: " + notif.text);
mContext.startActivity(intent);
}
private Intent getDlgIntent(GpsNiNotification notif)
{
Intent intent = new Intent();
String title = getDialogTitle(notif, mContext);
String message = getDialogMessage(notif, mContext);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.setClass(mContext, com.android.internal.app.NetInitiatedActivity.class);
intent.putExtra(NI_INTENT_KEY_NOTIF_ID, notif.notificationId);
intent.putExtra(NI_INTENT_KEY_TITLE, title);
intent.putExtra(NI_INTENT_KEY_MESSAGE, message);
intent.putExtra(NI_INTENT_KEY_TIMEOUT, notif.timeout);
intent.putExtra(NI_INTENT_KEY_DEFAULT_RESPONSE, notif.defaultResponse);
if (DEBUG) Log.d(TAG, "generateIntent, title: " + title + ", message: " + message +
", timeout: " + notif.timeout);
return intent;
}
static byte[] stringToByteArray(String original, boolean isHex)
{
int length = isHex ? original.length() / 2 : original.length();
byte[] output = new byte[length];
int i;
if (isHex)
{
for (i = 0; i < length; i++)
{
output[i] = (byte) Integer.parseInt(original.substring(i*2, i*2+2), 16);
}
}
else {
for (i = 0; i < length; i++)
{
output[i] = (byte) original.charAt(i);
}
}
return output;
}
static String decodeGSMPackedString(byte[] input)
{
final char PADDING_CHAR = 0x00;
int lengthBytes = input.length;
int lengthSeptets = (lengthBytes * 8) / 7;
String decoded;
if (lengthBytes % 7 == 0) {
if (lengthBytes > 0) {
if ((input[lengthBytes - 1] >> 1) == PADDING_CHAR) {
lengthSeptets = lengthSeptets - 1;
}
}
}
decoded = GsmAlphabet.gsm7BitPackedToString(input, 0, lengthSeptets);
if (null == decoded) {
Log.e(TAG, "Decoding of GSM packed string failed");
decoded = "";
}
return decoded;
}
static String decodeUTF8String(byte[] input)
{
String decoded = "";
try {
decoded = new String(input, "UTF-8");
}
catch (UnsupportedEncodingException e)
{
throw new AssertionError();
}
return decoded;
}
static String decodeUCS2String(byte[] input)
{
String decoded = "";
try {
decoded = new String(input, "UTF-16");
}
catch (UnsupportedEncodingException e)
{
throw new AssertionError();
}
return decoded;
}
static private String decodeString(String original, boolean isHex, int coding)
{
String decoded = original;
byte[] input = stringToByteArray(original, isHex);
switch (coding) {
case GPS_ENC_NONE:
decoded = original;
break;
case GPS_ENC_SUPL_GSM_DEFAULT:
decoded = decodeGSMPackedString(input);
break;
case GPS_ENC_SUPL_UTF8:
decoded = decodeUTF8String(input);
break;
case GPS_ENC_SUPL_UCS2:
decoded = decodeUCS2String(input);
break;
case GPS_ENC_UNKNOWN:
decoded = original;
break;
default:
Log.e(TAG, "Unknown encoding " + coding + " for NI text " + original);
break;
}
return decoded;
}
static private String getNotifTicker(GpsNiNotification notif, Context context)
{
String ticker = String.format(context.getString(R.string.gpsNotifTicker),
decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding),
decodeString(notif.text, mIsHexInput, notif.textEncoding));
return ticker;
}
static private String getNotifTitle(GpsNiNotification notif, Context context)
{
String title = String.format(context.getString(R.string.gpsNotifTitle));
return title;
}
static private String getNotifMessage(GpsNiNotification notif, Context context)
{
String message = String.format(context.getString(R.string.gpsNotifMessage),
decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding),
decodeString(notif.text, mIsHexInput, notif.textEncoding));
return message;
}
static public String getDialogTitle(GpsNiNotification notif, Context context)
{
return getNotifTitle(notif, context);
}
static private String getDialogMessage(GpsNiNotification notif, Context context)
{
return getNotifMessage(notif, context);
}
}