blob: a5466d1be03dfb1880921fac7afb91061f09271e [file] [log] [blame]
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.location;
import java.io.UnsupportedEncodingException;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
/**
* A GPS Network-initiated Handler class used by LocationManager.
*
* {@hide}
*/
public class GpsNetInitiatedHandler {
private static final String TAG = "GpsNetInitiatedHandler";
private static final boolean DEBUG = true;
private static final boolean VERBOSE = false;
// NI verify activity for bringing up UI (not used yet)
public static final String ACTION_NI_VERIFY = "android.intent.action.NETWORK_INITIATED_VERIFY";
// string constants for defining data fields in NI Intent
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";
// the extra command to send NI response to GpsLocationProvider
public static final String NI_RESPONSE_EXTRA_CMD = "send_ni_response";
// the extra command parameter names in the Bundle
public static final String NI_EXTRA_CMD_NOTIF_ID = "notif_id";
public static final String NI_EXTRA_CMD_RESPONSE = "response";
// these need to match GpsNiType constants in gps_ni.h
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;
// these need to match GpsUserResponseType constants in gps_ni.h
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;
// these need to match GpsNiNotifyFlags constants in gps_ni.h
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;
// these need to match GpsNiEncodingType in gps_ni.h
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 final Context mContext;
// parent gps location provider
private final GpsLocationProvider mGpsLocationProvider;
// configuration of notificaiton behavior
private boolean mPlaySounds = false;
private boolean visible = true;
private boolean mPopupImmediately = true;
// Set to true if string from HAL is encoded as Hex, e.g., "3F0039"
static private boolean mIsHexInput = true;
public static class GpsNiNotification
{
int notificationId;
int niType;
boolean needNotify;
boolean needVerify;
boolean privacyOverride;
int timeout;
int defaultResponse;
String requestorId;
String text;
int requestorIdEncoding;
int textEncoding;
Bundle extras;
};
public static class GpsNiResponse {
/* User reponse, one of the values in GpsUserResponseType */
int userResponse;
/* Optional extra data to pass with the user response */
Bundle extras;
};
/**
* The notification that is shown when a network-initiated notification
* (and verification) event is received.
* <p>
* This is lazily created, so use {@link #setNINotification()}.
*/
private Notification mNiNotification;
public GpsNetInitiatedHandler(Context context, GpsLocationProvider gpsLocationProvider) {
mContext = context;
mGpsLocationProvider = gpsLocationProvider;
}
// Handles NI events from HAL
public void handleNiNotification(GpsNiNotification notif)
{
if (DEBUG) Log.d(TAG, "handleNiNotification" + " notificationId: " + notif.notificationId
+ " requestorId: " + notif.requestorId + " text: " + notif.text);
// Notify and verify with immediate pop-up
if (notif.needNotify && notif.needVerify && mPopupImmediately)
{
// Popup the dialog box now
openNiDialog(notif);
}
// Notify only, or delayed pop-up (change mPopupImmediately to FALSE)
if (notif.needNotify && !notif.needVerify ||
notif.needNotify && notif.needVerify && !mPopupImmediately)
{
// Show the notification
// if mPopupImmediately == FALSE and needVerify == TRUE, a dialog will be opened
// when the user opens the notification message
setNiNotification(notif);
}
// ACCEPT cases: 1. Notify, no verify; 2. no notify, no verify; 3. privacy override.
if ( notif.needNotify && !notif.needVerify ||
!notif.needNotify && !notif.needVerify ||
notif.privacyOverride)
{
try {
mGpsLocationProvider.getNetInitiatedListener().sendNiResponse(notif.notificationId, GPS_NI_RESPONSE_ACCEPT);
}
catch (RemoteException e)
{
Log.e(TAG, e.getMessage());
}
}
//////////////////////////////////////////////////////////////////////////
// A note about timeout
// According to the protocol, in the need_notify and need_verify case,
// a default response should be sent when time out.
//
// In some GPS hardware, the GPS driver (under HAL) can handle the timeout case
// and this class GpsNetInitiatedHandler does not need to do anything.
//
// However, the UI should at least close the dialog when timeout. Further,
// for more general handling, timeout response should be added to the Handler here.
//
}
// Sets the NI notification.
private synchronized void setNiNotification(GpsNiNotification notif) {
NotificationManager notificationManager = (NotificationManager) mContext
.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager == null) {
return;
}
String title = getNotifTitle(notif);
String message = getNotifMessage(notif);
if (DEBUG) Log.d(TAG, "setNiNotification, notifyId: " + notif.notificationId +
", title: " + title +
", message: " + message);
// Construct Notification
if (mNiNotification == null) {
mNiNotification = new Notification();
mNiNotification.icon = com.android.internal.R.drawable.stat_sys_gps_on; /* Change notification icon here */
mNiNotification.when = 0;
}
if (mPlaySounds) {
mNiNotification.defaults |= Notification.DEFAULT_SOUND;
} else {
mNiNotification.defaults &= ~Notification.DEFAULT_SOUND;
}
mNiNotification.flags = Notification.FLAG_ONGOING_EVENT;
mNiNotification.tickerText = getNotifTicker(notif);
// if not to popup dialog immediately, pending intent will open the dialog
Intent intent = !mPopupImmediately ? getDlgIntent(notif) : new Intent();
PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);
mNiNotification.setLatestEventInfo(mContext, title, message, pi);
if (visible) {
notificationManager.notify(notif.notificationId, mNiNotification);
} else {
notificationManager.cancel(notif.notificationId);
}
}
// Opens the notification dialog and waits for user input
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);
}
// Construct the intent for bringing up the dialog activity, which shows the
// notification and takes user input
private Intent getDlgIntent(GpsNiNotification notif)
{
Intent intent = new Intent();
String title = getDialogTitle(notif);
String message = getDialogMessage(notif);
// directly bring up the NI activity
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClass(mContext, com.android.internal.app.NetInitiatedActivity.class);
// put data in the intent
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;
}
// Converts a string (or Hex string) to a char array
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;
}
/**
* Unpacks an byte array containing 7-bit packed characters into a String.
*
* @param input a 7-bit packed char array
* @return the unpacked String
*/
static String decodeGSMPackedString(byte[] input)
{
final char CHAR_CR = 0x0D;
int nStridx = 0;
int nPckidx = 0;
int num_bytes = input.length;
int cPrev = 0;
int cCurr = 0;
byte nShift;
byte nextChar;
byte[] stringBuf = new byte[input.length * 2];
String result = "";
while(nPckidx < num_bytes)
{
nShift = (byte) (nStridx & 0x07);
cCurr = input[nPckidx++];
if (cCurr < 0) cCurr += 256;
/* A 7-bit character can be split at the most between two bytes of packed
** data.
*/
nextChar = (byte) (( (cCurr << nShift) | (cPrev >> (8-nShift)) ) & 0x7F);
stringBuf[nStridx++] = nextChar;
/* Special case where the whole of the next 7-bit character fits inside
** the current byte of packed data.
*/
if(nShift == 6)
{
/* If the next 7-bit character is a CR (0x0D) and it is the last
** character, then it indicates a padding character. Drop it.
*/
if (nPckidx == num_bytes || (cCurr >> 1) == CHAR_CR)
{
break;
}
nextChar = (byte) (cCurr >> 1);
stringBuf[nStridx++] = nextChar;
}
cPrev = cCurr;
}
try{
result = new String(stringBuf, 0, nStridx, "US-ASCII");
}
catch (UnsupportedEncodingException e)
{
Log.e(TAG, e.getMessage());
}
return result;
}
static String decodeUTF8String(byte[] input)
{
String decoded = "";
try {
decoded = new String(input, "UTF-8");
}
catch (UnsupportedEncodingException e)
{
Log.e(TAG, e.getMessage());
}
return decoded;
}
static String decodeUCS2String(byte[] input)
{
String decoded = "";
try {
decoded = new String(input, "UTF-16");
}
catch (UnsupportedEncodingException e)
{
Log.e(TAG, e.getMessage());
}
return decoded;
}
/** Decode NI string
*
* @param original The text string to be decoded
* @param isHex Specifies whether the content of the string has been encoded as a Hex string. Encoding
* a string as Hex can allow zeros inside the coded text.
* @param coding Specifies the coding scheme of the string, such as GSM, UTF8, UCS2, etc. This coding scheme
* needs to match those used passed to HAL from the native GPS driver. Decoding is done according
* to the <code> coding </code>, after a Hex string is decoded. Generally, if the
* notification strings don't need further decoding, <code> coding </code> encoding can be
* set to -1, and <code> isHex </code> can be false.
* @return the decoded string
*/
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;
}
// change this to configure notification display
static private String getNotifTicker(GpsNiNotification notif)
{
String ticker = String.format("Position request! ReqId: [%s] ClientName: [%s]",
decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding),
decodeString(notif.text, mIsHexInput, notif.textEncoding));
return ticker;
}
// change this to configure notification display
static private String getNotifTitle(GpsNiNotification notif)
{
String title = String.format("Position Request");
return title;
}
// change this to configure notification display
static private String getNotifMessage(GpsNiNotification notif)
{
String message = String.format(
"NI Request received from [%s] for client [%s]!",
decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding),
decodeString(notif.text, mIsHexInput, notif.textEncoding));
return message;
}
// change this to configure dialog display (for verification)
static public String getDialogTitle(GpsNiNotification notif)
{
return getNotifTitle(notif);
}
// change this to configure dialog display (for verification)
static private String getDialogMessage(GpsNiNotification notif)
{
return getNotifMessage(notif);
}
}