From 9db3d07b9620b4269ab33f78604a36327e536ce1 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Queru Date: Thu, 12 Nov 2009 18:45:53 -0800 Subject: eclair snapshot --- location/java/android/location/Geocoder.java | 6 +- location/java/android/location/GpsStatus.java | 12 + .../java/android/location/IGpsStatusListener.aidl | 1 + .../java/android/location/ILocationManager.aidl | 3 + .../java/android/location/ILocationProvider.aidl | 3 +- .../android/location/INetInitiatedListener.aidl | 26 ++ .../java/android/location/LocationManager.java | 163 ++++++-- .../internal/location/GpsLocationProvider.java | 213 ++++++++-- .../internal/location/GpsNetInitiatedHandler.java | 457 +++++++++++++++++++++ .../internal/location/GpsXtraDownloader.java | 1 + .../internal/location/LocationProviderProxy.java | 5 +- .../android/internal/location/MockProvider.java | 3 +- 12 files changed, 826 insertions(+), 67 deletions(-) create mode 100755 location/java/android/location/INetInitiatedListener.aidl create mode 100755 location/java/com/android/internal/location/GpsNetInitiatedHandler.java (limited to 'location/java') diff --git a/location/java/android/location/Geocoder.java b/location/java/android/location/Geocoder.java index 53e46b7..2ce1273 100644 --- a/location/java/android/location/Geocoder.java +++ b/location/java/android/location/Geocoder.java @@ -36,11 +36,11 @@ import java.util.List; * coordinate into a (partial) address. The amount of detail in a * reverse geocoded location description may vary, for example one * might contain the full street address of the closest building, while - * another might contain only a city name and postal code. + * another might contain only a city name and postal code. * * The Geocoder class requires a backend service that is not included in - * the core android framework. The Geocoder query methods will return an - * empty list if there no backend service in the platform. + * the core android framework. The Geocoder query methods will return an + * empty list if there no backend service in the platform. */ public final class Geocoder { private static final String TAG = "Geocoder"; diff --git a/location/java/android/location/GpsStatus.java b/location/java/android/location/GpsStatus.java index 2cda7fa..ce69ac1 100644 --- a/location/java/android/location/GpsStatus.java +++ b/location/java/android/location/GpsStatus.java @@ -115,6 +115,18 @@ public final class GpsStatus { void onGpsStatusChanged(int event); } + /** + * Used for receiving NMEA sentences from the GPS. + * NMEA 0183 is a standard for communicating with marine electronic devices + * and is a common method for receiving data from a GPS, typically over a serial port. + * See NMEA 0183 for more details. + * You can implement this interface and call {@link LocationManager#addNmeaListener} + * to receive NMEA data from the GPS engine. + */ + public interface NmeaListener { + void onNmeaReceived(long timestamp, String nmea); + } + GpsStatus() { for (int i = 0; i < mSatellites.length; i++) { mSatellites[i] = new GpsSatellite(i + 1); diff --git a/location/java/android/location/IGpsStatusListener.aidl b/location/java/android/location/IGpsStatusListener.aidl index 5dc0fe8..62b1c6b 100644 --- a/location/java/android/location/IGpsStatusListener.aidl +++ b/location/java/android/location/IGpsStatusListener.aidl @@ -29,4 +29,5 @@ oneway interface IGpsStatusListener void onSvStatusChanged(int svCount, in int[] prns, in float[] snrs, in float[] elevations, in float[] azimuths, int ephemerisMask, int almanacMask, int usedInFixMask); + void onNmeaReceived(long timestamp, String nmea); } diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl index caf9516..b6c59d6 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -83,4 +83,7 @@ interface ILocationManager /* for installing external Location Providers */ void installLocationProvider(String name, ILocationProvider provider); void installGeocodeProvider(IGeocodeProvider provider); + + // for NI support + boolean sendNiResponse(int notifId, int userResponse); } diff --git a/location/java/android/location/ILocationProvider.aidl b/location/java/android/location/ILocationProvider.aidl index 4fe0494..7da16e4 100644 --- a/location/java/android/location/ILocationProvider.aidl +++ b/location/java/android/location/ILocationProvider.aidl @@ -17,6 +17,7 @@ package android.location; import android.location.Location; +import android.net.NetworkInfo; import android.os.Bundle; /** @@ -41,7 +42,7 @@ interface ILocationProvider { long getStatusUpdateTime(); void enableLocationTracking(boolean enable); void setMinTime(long minTime); - void updateNetworkState(int state); + void updateNetworkState(int state, in NetworkInfo info); void updateLocation(in Location location); boolean sendExtraCommand(String command, inout Bundle extras); void addListener(int uid); diff --git a/location/java/android/location/INetInitiatedListener.aidl b/location/java/android/location/INetInitiatedListener.aidl new file mode 100755 index 0000000..f2f5a32 --- /dev/null +++ b/location/java/android/location/INetInitiatedListener.aidl @@ -0,0 +1,26 @@ +/* +** +** Copyright 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 android.location; + +/** + * {@hide} + */ +interface INetInitiatedListener +{ + boolean sendNiResponse(int notifId, int userResponse); +} diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 86ea66f..94ced22 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -51,6 +51,8 @@ public class LocationManager { private ILocationManager mService; private final HashMap mGpsStatusListeners = new HashMap(); + private final HashMap mNmeaListeners = + new HashMap(); private final GpsStatus mGpsStatus = new GpsStatus(); /** @@ -68,7 +70,7 @@ public class LocationManager { * satellites. Depending on conditions, this provider may take a while to return * a location fix. * - * Requires the permission android.permissions.ACCESS_FINE_LOCATION. + * Requires the permission android.permission.ACCESS_FINE_LOCATION. * *

The extras Bundle for the GPS location provider can contain the * following key/value pairs: @@ -103,9 +105,6 @@ public class LocationManager { */ public static final String KEY_LOCATION_CHANGED = "location"; - /** @hide */ - public static final String SYSTEM_DIR = "/data/system/location"; - // Map from LocationListeners to their associated ListenerTransport objects private HashMap mListeners = new HashMap(); @@ -1123,49 +1122,103 @@ public class LocationManager { private class GpsStatusListenerTransport extends IGpsStatusListener.Stub { private final GpsStatus.Listener mListener; + private final GpsStatus.NmeaListener mNmeaListener; + + // This must not equal any of the GpsStatus event IDs + private static final int NMEA_RECEIVED = 1000; + + private class Nmea { + long mTimestamp; + String mNmea; + + Nmea(long timestamp, String nmea) { + mTimestamp = timestamp; + mNmea = nmea; + } + } + private ArrayList mNmeaBuffer; GpsStatusListenerTransport(GpsStatus.Listener listener) { mListener = listener; + mNmeaListener = null; + } + + GpsStatusListenerTransport(GpsStatus.NmeaListener listener) { + mNmeaListener = listener; + mListener = null; + mNmeaBuffer = new ArrayList(); } public void onGpsStarted() { - Message msg = Message.obtain(); - msg.what = GpsStatus.GPS_EVENT_STARTED; - mGpsHandler.sendMessage(msg); + if (mListener != null) { + Message msg = Message.obtain(); + msg.what = GpsStatus.GPS_EVENT_STARTED; + mGpsHandler.sendMessage(msg); + } } public void onGpsStopped() { - Message msg = Message.obtain(); - msg.what = GpsStatus.GPS_EVENT_STOPPED; - mGpsHandler.sendMessage(msg); + if (mListener != null) { + Message msg = Message.obtain(); + msg.what = GpsStatus.GPS_EVENT_STOPPED; + mGpsHandler.sendMessage(msg); + } } public void onFirstFix(int ttff) { - mGpsStatus.setTimeToFirstFix(ttff); - Message msg = Message.obtain(); - msg.what = GpsStatus.GPS_EVENT_FIRST_FIX; - mGpsHandler.sendMessage(msg); + if (mListener != null) { + mGpsStatus.setTimeToFirstFix(ttff); + Message msg = Message.obtain(); + msg.what = GpsStatus.GPS_EVENT_FIRST_FIX; + mGpsHandler.sendMessage(msg); + } } public void onSvStatusChanged(int svCount, int[] prns, float[] snrs, float[] elevations, float[] azimuths, int ephemerisMask, int almanacMask, int usedInFixMask) { - mGpsStatus.setStatus(svCount, prns, snrs, elevations, azimuths, - ephemerisMask, almanacMask, usedInFixMask); + if (mListener != null) { + mGpsStatus.setStatus(svCount, prns, snrs, elevations, azimuths, + ephemerisMask, almanacMask, usedInFixMask); + + Message msg = Message.obtain(); + msg.what = GpsStatus.GPS_EVENT_SATELLITE_STATUS; + // remove any SV status messages already in the queue + mGpsHandler.removeMessages(GpsStatus.GPS_EVENT_SATELLITE_STATUS); + mGpsHandler.sendMessage(msg); + } + } - Message msg = Message.obtain(); - msg.what = GpsStatus.GPS_EVENT_SATELLITE_STATUS; - // remove any SV status messages already in the queue - mGpsHandler.removeMessages(GpsStatus.GPS_EVENT_SATELLITE_STATUS); - mGpsHandler.sendMessage(msg); + public void onNmeaReceived(long timestamp, String nmea) { + if (mNmeaListener != null) { + synchronized (mNmeaBuffer) { + mNmeaBuffer.add(new Nmea(timestamp, nmea)); + } + Message msg = Message.obtain(); + msg.what = NMEA_RECEIVED; + // remove any NMEA_RECEIVED messages already in the queue + mGpsHandler.removeMessages(NMEA_RECEIVED); + mGpsHandler.sendMessage(msg); + } } private final Handler mGpsHandler = new Handler() { @Override public void handleMessage(Message msg) { - // synchronize on mGpsStatus to ensure the data is copied atomically. - synchronized(mGpsStatus) { - mListener.onGpsStatusChanged(msg.what); + if (msg.what == NMEA_RECEIVED) { + synchronized (mNmeaBuffer) { + int length = mNmeaBuffer.size(); + for (int i = 0; i < length; i++) { + Nmea nmea = mNmeaBuffer.get(i); + mNmeaListener.onNmeaReceived(nmea.mTimestamp, nmea.mNmea); + } + mNmeaBuffer.clear(); + } + } else { + // synchronize on mGpsStatus to ensure the data is copied atomically. + synchronized(mGpsStatus) { + mListener.onGpsStatusChanged(msg.what); + } } } }; @@ -1217,6 +1270,52 @@ public class LocationManager { } } + /** + * Adds an NMEA listener. + * + * @param listener a {#link GpsStatus.NmeaListener} object to register + * + * @return true if the listener was successfully added + * + * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present + */ + public boolean addNmeaListener(GpsStatus.NmeaListener listener) { + boolean result; + + if (mNmeaListeners.get(listener) != null) { + // listener is already registered + return true; + } + try { + GpsStatusListenerTransport transport = new GpsStatusListenerTransport(listener); + result = mService.addGpsStatusListener(transport); + if (result) { + mNmeaListeners.put(listener, transport); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in registerGpsStatusListener: ", e); + result = false; + } + + return result; + } + + /** + * Removes an NMEA listener. + * + * @param listener a {#link GpsStatus.NmeaListener} object to remove + */ + public void removeNmeaListener(GpsStatus.NmeaListener listener) { + try { + GpsStatusListenerTransport transport = mNmeaListeners.remove(listener); + if (transport != null) { + mService.removeGpsStatusListener(transport); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in unregisterGpsStatusListener: ", e); + } + } + /** * Retrieves information about the current status of the GPS engine. * This should only be called from the {@link GpsStatus.Listener#onGpsStatusChanged} @@ -1315,4 +1414,20 @@ public class LocationManager { Log.e(TAG, "RemoteException in reportLocation: ", e); } } + + /** + * Used by NetInitiatedActivity to report user response + * for network initiated GPS fix requests. + * + * {@hide} + */ + public boolean sendNiResponse(int notifId, int userResponse) { + try { + return mService.sendNiResponse(notifId, userResponse); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in sendNiResponse: ", e); + return false; + } + } + } diff --git a/location/java/com/android/internal/location/GpsLocationProvider.java b/location/java/com/android/internal/location/GpsLocationProvider.java index 4a51e31..cd62ed1 100755 --- a/location/java/com/android/internal/location/GpsLocationProvider.java +++ b/location/java/com/android/internal/location/GpsLocationProvider.java @@ -27,10 +27,12 @@ import android.location.IGpsStatusListener; import android.location.IGpsStatusProvider; import android.location.ILocationManager; import android.location.ILocationProvider; +import android.location.INetInitiatedListener; import android.location.Location; import android.location.LocationManager; import android.location.LocationProvider; import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.net.SntpClient; import android.os.Bundle; import android.os.IBinder; @@ -38,21 +40,25 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.provider.Settings; import android.util.Config; import android.util.Log; import android.util.SparseIntArray; import com.android.internal.app.IBatteryStats; import com.android.internal.telephony.Phone; -import com.android.internal.telephony.TelephonyIntents; +import com.android.internal.location.GpsNetInitiatedHandler; +import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.io.StringBufferInputStream; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Properties; +import java.util.Map.Entry; /** * A GPS implementation of LocationProvider used by LocationManager. @@ -183,8 +189,6 @@ public class GpsLocationProvider extends ILocationProvider.Stub { // number of fixes we have received since we started navigating private int mFixCount; - private int mPositionMode = GPS_POSITION_MODE_STANDALONE; - // true if we started navigation private boolean mStarted; @@ -198,6 +202,10 @@ public class GpsLocationProvider extends ILocationProvider.Stub { // properties loaded from PROPERTIES_FILE private Properties mProperties; private String mNtpServer; + private String mSuplServerHost; + private int mSuplServerPort; + private String mC2KServerHost; + private int mC2KServerPort; private final Context mContext; private final ILocationManager mLocationManager; @@ -211,6 +219,7 @@ public class GpsLocationProvider extends ILocationProvider.Stub { private String mAGpsApn; private int mAGpsDataConnectionState; private final ConnectivityManager mConnMgr; + private final GpsNetInitiatedHandler mNIHandler; // Wakelocks private final static String WAKELOCK_KEY = "GpsLocationProvider"; @@ -294,22 +303,6 @@ public class GpsLocationProvider extends ILocationProvider.Stub { } else if (action.equals(ALARM_TIMEOUT)) { if (DEBUG) Log.d(TAG, "ALARM_TIMEOUT"); hibernate(); - } else if (action.equals(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) { - String state = intent.getStringExtra(Phone.STATE_KEY); - String apnName = intent.getStringExtra(Phone.DATA_APN_KEY); - String reason = intent.getStringExtra(Phone.STATE_CHANGE_REASON_KEY); - - if (Config.LOGD) { - Log.d(TAG, "state: " + state + " apnName: " + apnName + " reason: " + reason); - } - // FIXME - might not have an APN on CDMA - if ("CONNECTED".equals(state) && apnName != null && apnName.length() > 0) { - mAGpsApn = apnName; - if (mAGpsDataConnectionState == AGPS_DATA_CONNECTION_OPENING) { - native_agps_data_conn_open(mAGpsApn); - mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN; - } - } } } }; @@ -321,6 +314,7 @@ public class GpsLocationProvider extends ILocationProvider.Stub { public GpsLocationProvider(Context context, ILocationManager locationManager) { mContext = context; mLocationManager = locationManager; + mNIHandler= new GpsNetInitiatedHandler(context, this); // Create a wake lock PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); @@ -333,7 +327,6 @@ public class GpsLocationProvider extends ILocationProvider.Stub { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(ALARM_WAKEUP); intentFilter.addAction(ALARM_TIMEOUT); - intentFilter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED); context.registerReceiver(mBroadcastReciever, intentFilter); mConnMgr = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); @@ -349,27 +342,21 @@ public class GpsLocationProvider extends ILocationProvider.Stub { stream.close(); mNtpServer = mProperties.getProperty("NTP_SERVER", null); - String host = mProperties.getProperty("SUPL_HOST"); + mSuplServerHost = mProperties.getProperty("SUPL_HOST"); String portString = mProperties.getProperty("SUPL_PORT"); - if (host != null && portString != null) { + if (mSuplServerHost != null && portString != null) { try { - int port = Integer.parseInt(portString); - native_set_agps_server(AGPS_TYPE_SUPL, host, port); - // use MS-Based position mode if SUPL support is enabled - mPositionMode = GPS_POSITION_MODE_MS_BASED; + mSuplServerPort = Integer.parseInt(portString); } catch (NumberFormatException e) { Log.e(TAG, "unable to parse SUPL_PORT: " + portString); } } - host = mProperties.getProperty("C2K_HOST"); + mC2KServerHost = mProperties.getProperty("C2K_HOST"); portString = mProperties.getProperty("C2K_PORT"); - if (host != null && portString != null) { + if (mC2KServerHost != null && portString != null) { try { - int port = Integer.parseInt(portString); - native_set_agps_server(AGPS_TYPE_C2K, host, port); - // use MS-Based position mode if SUPL support is enabled - mPositionMode = GPS_POSITION_MODE_MS_BASED; + mC2KServerPort = Integer.parseInt(portString); } catch (NumberFormatException e) { Log.e(TAG, "unable to parse C2K_PORT: " + portString); } @@ -387,13 +374,30 @@ public class GpsLocationProvider extends ILocationProvider.Stub { return true; } - public void updateNetworkState(int state) { + public void updateNetworkState(int state, NetworkInfo info) { mNetworkAvailable = (state == LocationProvider.AVAILABLE); if (Config.LOGD) { - Log.d(TAG, "updateNetworkState " + (mNetworkAvailable ? "available" : "unavailable")); + Log.d(TAG, "updateNetworkState " + (mNetworkAvailable ? "available" : "unavailable") + + " info: " + info); } - + + if (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE_SUPL + && mAGpsDataConnectionState == AGPS_DATA_CONNECTION_OPENING) { + String apnName = info.getExtraInfo(); + if (mNetworkAvailable && apnName != null && apnName.length() > 0) { + mAGpsApn = apnName; + if (DEBUG) Log.d(TAG, "call native_agps_data_conn_open"); + native_agps_data_conn_open(apnName); + mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN; + } else { + if (DEBUG) Log.d(TAG, "call native_agps_data_conn_failed"); + mAGpsApn = null; + mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED; + native_agps_data_conn_failed(); + } + } + if (mNetworkAvailable && mNetworkThread != null && mEnabled) { // signal the network thread when the network becomes available mNetworkThread.signal(); @@ -499,6 +503,13 @@ public class GpsLocationProvider extends ILocationProvider.Stub { mEnabled = native_init(); if (mEnabled) { + if (mSuplServerHost != null) { + native_set_agps_server(AGPS_TYPE_SUPL, mSuplServerHost, mSuplServerPort); + } + if (mC2KServerHost != null) { + native_set_agps_server(AGPS_TYPE_C2K, mC2KServerHost, mC2KServerPort); + } + // run event listener thread while we are enabled mEventThread = new GpsEventThread(); mEventThread.start(); @@ -722,7 +733,15 @@ public class GpsLocationProvider extends ILocationProvider.Stub { if (!mStarted) { if (DEBUG) Log.d(TAG, "startNavigating"); mStarted = true; - if (!native_start(mPositionMode, false, mFixInterval)) { + int positionMode; + if (Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.ASSISTED_GPS_ENABLED, 1) != 0) { + positionMode = GPS_POSITION_MODE_MS_BASED; + } else { + positionMode = GPS_POSITION_MODE_STANDALONE; + } + + if (!native_start(positionMode, false, mFixInterval)) { mStarted = false; Log.e(TAG, "native_start failed in startNavigating()"); return; @@ -1002,6 +1021,32 @@ public class GpsLocationProvider extends ILocationProvider.Stub { } } + /** + * called from native code to report NMEA data received + */ + private void reportNmea(int index, long timestamp) { + synchronized(mListeners) { + int size = mListeners.size(); + if (size > 0) { + // don't bother creating the String if we have no listeners + int length = native_read_nmea(index, mNmeaBuffer, mNmeaBuffer.length); + String nmea = new String(mNmeaBuffer, 0, length); + + for (int i = 0; i < size; i++) { + Listener listener = mListeners.get(i); + try { + listener.mListener.onNmeaReceived(timestamp, nmea); + } catch (RemoteException e) { + Log.w(TAG, "RemoteException in reportNmea"); + mListeners.remove(listener); + // adjust for size of list changing + size--; + } + } + } + } + } + private void xtraDownloadRequest() { if (Config.LOGD) Log.d(TAG, "xtraDownloadRequest"); if (mNetworkThread != null) { @@ -1009,6 +1054,96 @@ public class GpsLocationProvider extends ILocationProvider.Stub { } } + //============================================================= + // NI Client support + //============================================================= + private final INetInitiatedListener mNetInitiatedListener = new INetInitiatedListener.Stub() { + // Sends a response for an NI reqeust to HAL. + public boolean sendNiResponse(int notificationId, int userResponse) + { + // TODO Add Permission check + + StringBuilder extrasBuf = new StringBuilder(); + + if (Config.LOGD) Log.d(TAG, "sendNiResponse, notifId: " + notificationId + + ", response: " + userResponse); + + native_send_ni_response(notificationId, userResponse); + + return true; + } + }; + + public INetInitiatedListener getNetInitiatedListener() { + return mNetInitiatedListener; + } + + // Called by JNI function to report an NI request. + @SuppressWarnings("deprecation") + public void reportNiNotification( + int notificationId, + int niType, + int notifyFlags, + int timeout, + int defaultResponse, + String requestorId, + String text, + int requestorIdEncoding, + int textEncoding, + String extras // Encoded extra data + ) + { + Log.i(TAG, "reportNiNotification: entered"); + Log.i(TAG, "notificationId: " + notificationId + + ", niType: " + niType + + ", notifyFlags: " + notifyFlags + + ", timeout: " + timeout + + ", defaultResponse: " + defaultResponse); + + Log.i(TAG, "requestorId: " + requestorId + + ", text: " + text + + ", requestorIdEncoding: " + requestorIdEncoding + + ", textEncoding: " + textEncoding); + + GpsNiNotification notification = new GpsNiNotification(); + + notification.notificationId = notificationId; + notification.niType = niType; + notification.needNotify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_NOTIFY) != 0; + notification.needVerify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_VERIFY) != 0; + notification.privacyOverride = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_PRIVACY_OVERRIDE) != 0; + notification.timeout = timeout; + notification.defaultResponse = defaultResponse; + notification.requestorId = requestorId; + notification.text = text; + notification.requestorIdEncoding = requestorIdEncoding; + notification.textEncoding = textEncoding; + + // Process extras, assuming the format is + // one of more lines of "key = value" + Bundle bundle = new Bundle(); + + if (extras == null) extras = ""; + Properties extraProp = new Properties(); + + try { + extraProp.load(new StringBufferInputStream(extras)); + } + catch (IOException e) + { + Log.e(TAG, "reportNiNotification cannot parse extras data: " + extras); + } + + for (Entry ent : extraProp.entrySet()) + { + bundle.putString((String) ent.getKey(), (String) ent.getValue()); + } + + notification.extras = bundle; + + mNIHandler.handleNiNotification(notification); + } + private class GpsEventThread extends Thread { public GpsEventThread() { @@ -1182,6 +1317,8 @@ public class GpsLocationProvider extends ILocationProvider.Stub { private float mSvAzimuths[] = new float[MAX_SVS]; private int mSvMasks[] = new int[3]; private int mSvCount; + // preallocated to avoid memory allocation in reportNmea() + private byte[] mNmeaBuffer = new byte[120]; static { class_init_native(); } private static native void class_init_native(); @@ -1199,6 +1336,7 @@ public class GpsLocationProvider extends ILocationProvider.Stub { // mask[0] is ephemeris mask and mask[1] is almanac mask private native int native_read_sv_status(int[] svs, float[] snrs, float[] elevations, float[] azimuths, int[] masks); + private native int native_read_nmea(int index, byte[] buffer, int bufferSize); private native void native_inject_location(double latitude, double longitude, float accuracy); // XTRA Support @@ -1211,4 +1349,7 @@ public class GpsLocationProvider extends ILocationProvider.Stub { private native void native_agps_data_conn_closed(); private native void native_agps_data_conn_failed(); private native void native_set_agps_server(int type, String hostname, int port); + + // Network-initiated (NI) Support + private native void native_send_ni_response(int notificationId, int userResponse); } diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java new file mode 100755 index 0000000..a5466d1 --- /dev/null +++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java @@ -0,0 +1,457 @@ +/* + * 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. + *

+ * 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 coding , after a Hex string is decoded. Generally, if the + * notification strings don't need further decoding, coding encoding can be + * set to -1, and isHex 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); + } + +} diff --git a/location/java/com/android/internal/location/GpsXtraDownloader.java b/location/java/com/android/internal/location/GpsXtraDownloader.java index 2a8be57..33ebce7 100644 --- a/location/java/com/android/internal/location/GpsXtraDownloader.java +++ b/location/java/com/android/internal/location/GpsXtraDownloader.java @@ -64,6 +64,7 @@ public class GpsXtraDownloader { if (count == 0) { Log.e(TAG, "No XTRA servers were specified in the GPS configuration"); + return; } else { mXtraServers = new String[count]; count = 0; diff --git a/location/java/com/android/internal/location/LocationProviderProxy.java b/location/java/com/android/internal/location/LocationProviderProxy.java index 4ae424a..89337b3 100644 --- a/location/java/com/android/internal/location/LocationProviderProxy.java +++ b/location/java/com/android/internal/location/LocationProviderProxy.java @@ -20,6 +20,7 @@ import android.location.Address; import android.location.ILocationProvider; import android.location.Location; import android.location.LocationManager; +import android.net.NetworkInfo; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; @@ -217,9 +218,9 @@ public class LocationProviderProxy implements IBinder.DeathRecipient { } } - public void updateNetworkState(int state) { + public void updateNetworkState(int state, NetworkInfo info) { try { - mProvider.updateNetworkState(state); + mProvider.updateNetworkState(state, info); } catch (RemoteException e) { Log.e(TAG, "updateNetworkState failed", e); } diff --git a/location/java/com/android/internal/location/MockProvider.java b/location/java/com/android/internal/location/MockProvider.java index e2e0562..2614f82 100644 --- a/location/java/com/android/internal/location/MockProvider.java +++ b/location/java/com/android/internal/location/MockProvider.java @@ -20,6 +20,7 @@ import android.location.ILocationManager; import android.location.ILocationProvider; import android.location.Location; import android.location.LocationProvider; +import android.net.NetworkInfo; import android.os.Bundle; import android.os.RemoteException; import android.util.Log; @@ -169,7 +170,7 @@ public class MockProvider extends ILocationProvider.Stub { public void setMinTime(long minTime) { } - public void updateNetworkState(int state) { + public void updateNetworkState(int state, NetworkInfo info) { } public void updateLocation(Location location) { -- cgit v1.1