Move files internal to LocationManagerService from framework.jar to services.jar

Change-Id: I3dbb40210d87708e0bff46729f707d4ab8e29e42
Signed-off-by: Mike Lockwood <lockwood@android.com>
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index ef57056..f9c1a93 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -16,17 +16,6 @@
 
 package com.android.server;
 
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Observable;
-import java.util.Observer;
-import java.util.Set;
-
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -50,7 +39,6 @@
 import android.location.Location;
 import android.location.LocationManager;
 import android.location.LocationProvider;
-import android.location.LocationProviderInterface;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.net.Uri;
@@ -68,12 +56,25 @@
 import android.util.Slog;
 import android.util.PrintWriterPrinter;
 
-import com.android.internal.location.GeocoderProxy;
-import com.android.internal.location.GpsLocationProvider;
 import com.android.internal.location.GpsNetInitiatedHandler;
-import com.android.internal.location.LocationProviderProxy;
-import com.android.internal.location.MockProvider;
-import com.android.internal.location.PassiveProvider;
+
+import com.android.server.location.GeocoderProxy;
+import com.android.server.location.GpsLocationProvider;
+import com.android.server.location.LocationProviderInterface;
+import com.android.server.location.LocationProviderProxy;
+import com.android.server.location.MockProvider;
+import com.android.server.location.PassiveProvider;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Observable;
+import java.util.Observer;
+import java.util.Set;
 
 /**
  * The service class that manages LocationProviders and issues location
diff --git a/services/java/com/android/server/location/GeocoderProxy.java b/services/java/com/android/server/location/GeocoderProxy.java
new file mode 100644
index 0000000..3c05da2
--- /dev/null
+++ b/services/java/com/android/server/location/GeocoderProxy.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2010 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.server.location;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.location.Address;
+import android.location.GeocoderParams;
+import android.location.IGeocodeProvider;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * A class for proxying IGeocodeProvider implementations.
+ *
+ * {@hide}
+ */
+public class GeocoderProxy {
+
+    private static final String TAG = "GeocoderProxy";
+
+    private final Context mContext;
+    private final Intent mIntent;
+    private final Connection mServiceConnection = new Connection();
+    private IGeocodeProvider mProvider;
+
+    public GeocoderProxy(Context context, String serviceName) {
+        mContext = context;
+        mIntent = new Intent(serviceName);
+        mContext.bindService(mIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
+    }
+
+    private class Connection implements ServiceConnection {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            Log.d(TAG, "onServiceConnected " + className);
+            synchronized (this) {
+                mProvider = IGeocodeProvider.Stub.asInterface(service);
+            }
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            Log.d(TAG, "onServiceDisconnected " + className);
+            synchronized (this) {
+                mProvider = null;
+            }
+        }
+    }
+
+    public String getFromLocation(double latitude, double longitude, int maxResults,
+            GeocoderParams params, List<Address> addrs) {
+        IGeocodeProvider provider;
+        synchronized (mServiceConnection) {
+            provider = mProvider;
+        }
+        if (provider != null) {
+            try {
+                return provider.getFromLocation(latitude, longitude, maxResults,
+                        params, addrs);
+            } catch (RemoteException e) {
+                Log.e(TAG, "getFromLocation failed", e);
+            }
+        }
+        return "Service not Available";
+    }
+
+    public String getFromLocationName(String locationName,
+            double lowerLeftLatitude, double lowerLeftLongitude,
+            double upperRightLatitude, double upperRightLongitude, int maxResults,
+            GeocoderParams params, List<Address> addrs) {
+        IGeocodeProvider provider;
+        synchronized (mServiceConnection) {
+            provider = mProvider;
+        }
+        if (provider != null) {
+            try {
+                return provider.getFromLocationName(locationName, lowerLeftLatitude,
+                        lowerLeftLongitude, upperRightLatitude, upperRightLongitude,
+                        maxResults, params, addrs);
+            } catch (RemoteException e) {
+                Log.e(TAG, "getFromLocationName failed", e);
+            }
+        }
+        return "Service not Available";
+    }
+}
diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java
new file mode 100755
index 0000000..daa198f
--- /dev/null
+++ b/services/java/com/android/server/location/GpsLocationProvider.java
@@ -0,0 +1,1439 @@
+/*
+ * 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.server.location;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.location.Criteria;
+import android.location.IGpsStatusListener;
+import android.location.IGpsStatusProvider;
+import android.location.ILocationManager;
+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.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.telephony.Phone;
+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.Date;
+import java.util.Properties;
+import java.util.Map.Entry;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * A GPS implementation of LocationProvider used by LocationManager.
+ *
+ * {@hide}
+ */
+public class GpsLocationProvider implements LocationProviderInterface {
+
+    private static final String TAG = "GpsLocationProvider";
+
+    private static final boolean DEBUG = false;
+    private static final boolean VERBOSE = false;
+
+    // these need to match GpsPositionMode enum in gps.h
+    private static final int GPS_POSITION_MODE_STANDALONE = 0;
+    private static final int GPS_POSITION_MODE_MS_BASED = 1;
+    private static final int GPS_POSITION_MODE_MS_ASSISTED = 2;
+
+    // these need to match GpsStatusValue defines in gps.h
+    private static final int GPS_STATUS_NONE = 0;
+    private static final int GPS_STATUS_SESSION_BEGIN = 1;
+    private static final int GPS_STATUS_SESSION_END = 2;
+    private static final int GPS_STATUS_ENGINE_ON = 3;
+    private static final int GPS_STATUS_ENGINE_OFF = 4;
+
+    // these need to match GpsApgsStatusValue defines in gps.h
+    /** AGPS status event values. */
+    private static final int GPS_REQUEST_AGPS_DATA_CONN = 1;
+    private static final int GPS_RELEASE_AGPS_DATA_CONN = 2;
+    private static final int GPS_AGPS_DATA_CONNECTED = 3;
+    private static final int GPS_AGPS_DATA_CONN_DONE = 4;
+    private static final int GPS_AGPS_DATA_CONN_FAILED = 5;
+
+    // these need to match GpsLocationFlags enum in gps.h
+    private static final int LOCATION_INVALID = 0;
+    private static final int LOCATION_HAS_LAT_LONG = 1;
+    private static final int LOCATION_HAS_ALTITUDE = 2;
+    private static final int LOCATION_HAS_SPEED = 4;
+    private static final int LOCATION_HAS_BEARING = 8;
+    private static final int LOCATION_HAS_ACCURACY = 16;
+
+// IMPORTANT - the GPS_DELETE_* symbols here must match constants in gps.h
+    private static final int GPS_DELETE_EPHEMERIS = 0x0001;
+    private static final int GPS_DELETE_ALMANAC = 0x0002;
+    private static final int GPS_DELETE_POSITION = 0x0004;
+    private static final int GPS_DELETE_TIME = 0x0008;
+    private static final int GPS_DELETE_IONO = 0x0010;
+    private static final int GPS_DELETE_UTC = 0x0020;
+    private static final int GPS_DELETE_HEALTH = 0x0040;
+    private static final int GPS_DELETE_SVDIR = 0x0080;
+    private static final int GPS_DELETE_SVSTEER = 0x0100;
+    private static final int GPS_DELETE_SADATA = 0x0200;
+    private static final int GPS_DELETE_RTI = 0x0400;
+    private static final int GPS_DELETE_CELLDB_INFO = 0x8000;
+    private static final int GPS_DELETE_ALL = 0xFFFF;
+
+    // these need to match AGpsType enum in gps.h
+    private static final int AGPS_TYPE_SUPL = 1;
+    private static final int AGPS_TYPE_C2K = 2;
+
+    // for mAGpsDataConnectionState
+    private static final int AGPS_DATA_CONNECTION_CLOSED = 0;
+    private static final int AGPS_DATA_CONNECTION_OPENING = 1;
+    private static final int AGPS_DATA_CONNECTION_OPEN = 2;
+
+    // Handler messages
+    private static final int CHECK_LOCATION = 1;
+    private static final int ENABLE = 2;
+    private static final int ENABLE_TRACKING = 3;
+    private static final int UPDATE_NETWORK_STATE = 4;
+    private static final int INJECT_NTP_TIME = 5;
+    private static final int DOWNLOAD_XTRA_DATA = 6;
+    private static final int UPDATE_LOCATION = 7;
+    private static final int ADD_LISTENER = 8;
+    private static final int REMOVE_LISTENER = 9;
+
+    private static final String PROPERTIES_FILE = "/etc/gps.conf";
+
+    private int mLocationFlags = LOCATION_INVALID;
+
+    // current status
+    private int mStatus = LocationProvider.TEMPORARILY_UNAVAILABLE;
+
+    // time for last status update
+    private long mStatusUpdateTime = SystemClock.elapsedRealtime();
+    
+    // turn off GPS fix icon if we haven't received a fix in 10 seconds
+    private static final long RECENT_FIX_TIMEOUT = 10;
+    
+    // number of fixes to receive before disabling GPS
+    private static final int MIN_FIX_COUNT = 10;
+
+    // stop trying if we do not receive a fix within 60 seconds
+    private static final int NO_FIX_TIMEOUT = 60;
+
+    // true if we are enabled
+    private volatile boolean mEnabled;
+    
+    // true if we have network connectivity
+    private boolean mNetworkAvailable;
+
+    // flags to trigger NTP or XTRA data download when network becomes available
+    // initialized to true so we do NTP and XTRA when the network comes up after booting
+    private boolean mInjectNtpTimePending = true;
+    private boolean mDownloadXtraDataPending = true;
+
+    // true if GPS is navigating
+    private boolean mNavigating;
+
+    // true if GPS engine is on
+    private boolean mEngineOn;
+    
+    // requested frequency of fixes, in seconds
+    private int mFixInterval = 1;
+
+    // number of fixes we have received since we started navigating
+    private int mFixCount;
+
+    // true if we started navigation
+    private boolean mStarted;
+
+    // for calculating time to first fix
+    private long mFixRequestTime = 0;
+    // time to first fix for most recent session
+    private int mTTFF = 0;
+    // time we received our last fix
+    private long mLastFixTime;
+
+    // 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;
+    private Location mLocation = new Location(LocationManager.GPS_PROVIDER);
+    private Bundle mLocationExtras = new Bundle();
+    private ArrayList<Listener> mListeners = new ArrayList<Listener>();
+
+    // GpsLocationProvider's handler thread
+    private final Thread mThread;
+    // Handler for processing events in mThread.
+    private Handler mHandler;
+    // Used to signal when our main thread has initialized everything
+    private final CountDownLatch mInitializedLatch = new CountDownLatch(1);
+    // Thread for receiving events from the native code
+    private Thread mEventThread;
+
+    private String mAGpsApn;
+    private int mAGpsDataConnectionState;
+    private final ConnectivityManager mConnMgr;
+    private final GpsNetInitiatedHandler mNIHandler; 
+
+    // Wakelocks
+    private final static String WAKELOCK_KEY = "GpsLocationProvider";
+    private final PowerManager.WakeLock mWakeLock;
+
+    // Alarms
+    private final static String ALARM_WAKEUP = "com.android.internal.location.ALARM_WAKEUP";
+    private final static String ALARM_TIMEOUT = "com.android.internal.location.ALARM_TIMEOUT";
+    private final AlarmManager mAlarmManager;
+    private final PendingIntent mWakeupIntent;
+    private final PendingIntent mTimeoutIntent;
+
+    private final IBatteryStats mBatteryStats;
+    private final SparseIntArray mClientUids = new SparseIntArray();
+
+    // how often to request NTP time, in milliseconds
+    // current setting 4 hours
+    private static final long NTP_INTERVAL = 4*60*60*1000;
+    // how long to wait if we have a network error in NTP or XTRA downloading
+    // current setting - 5 minutes
+    private static final long RETRY_INTERVAL = 5*60*1000;
+
+    // to avoid injecting bad NTP time, we reject any time fixes that differ from system time
+    // by more than 5 minutes.
+    private static final long MAX_NTP_SYSTEM_TIME_OFFSET = 5*60*1000;
+
+    private final IGpsStatusProvider mGpsStatusProvider = new IGpsStatusProvider.Stub() {
+        public void addGpsStatusListener(IGpsStatusListener listener) throws RemoteException {
+            if (listener == null) {
+                throw new NullPointerException("listener is null in addGpsStatusListener");
+            }
+
+            synchronized(mListeners) {
+                IBinder binder = listener.asBinder();
+                int size = mListeners.size();
+                for (int i = 0; i < size; i++) {
+                    Listener test = mListeners.get(i);
+                    if (binder.equals(test.mListener.asBinder())) {
+                        // listener already added
+                        return;
+                    }
+                }
+
+                Listener l = new Listener(listener);
+                binder.linkToDeath(l, 0);
+                mListeners.add(l);
+            }
+        }
+
+        public void removeGpsStatusListener(IGpsStatusListener listener) {
+            if (listener == null) {
+                throw new NullPointerException("listener is null in addGpsStatusListener");
+            }
+
+            synchronized(mListeners) {
+                IBinder binder = listener.asBinder();
+                Listener l = null;
+                int size = mListeners.size();
+                for (int i = 0; i < size && l == null; i++) {
+                    Listener test = mListeners.get(i);
+                    if (binder.equals(test.mListener.asBinder())) {
+                        l = test;
+                    }
+                }
+
+                if (l != null) {
+                    mListeners.remove(l);
+                    binder.unlinkToDeath(l, 0);
+                }
+            }
+        }
+    };
+
+    public IGpsStatusProvider getGpsStatusProvider() {
+        return mGpsStatusProvider;
+    }
+
+    private final BroadcastReceiver mBroadcastReciever = new BroadcastReceiver() {
+        @Override public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+
+            if (action.equals(ALARM_WAKEUP)) {
+                if (DEBUG) Log.d(TAG, "ALARM_WAKEUP");
+                startNavigating();
+            } else if (action.equals(ALARM_TIMEOUT)) {
+                if (DEBUG) Log.d(TAG, "ALARM_TIMEOUT");
+                hibernate();
+            }
+        }
+    };
+
+    public static boolean isSupported() {
+        return native_is_supported();
+    }
+
+    public GpsLocationProvider(Context context, ILocationManager locationManager) {
+        mContext = context;
+        mLocationManager = locationManager;
+        mNIHandler = new GpsNetInitiatedHandler(context);
+
+        mLocation.setExtras(mLocationExtras);
+
+        // Create a wake lock
+        PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
+
+        mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+        mWakeupIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_WAKEUP), 0);
+        mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_TIMEOUT), 0);
+
+        mConnMgr = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
+
+        // Battery statistics service to be notified when GPS turns on or off
+        mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
+
+        mProperties = new Properties();
+        try {
+            File file = new File(PROPERTIES_FILE);
+            FileInputStream stream = new FileInputStream(file);
+            mProperties.load(stream);
+            stream.close();
+            mNtpServer = mProperties.getProperty("NTP_SERVER", null);
+
+            mSuplServerHost = mProperties.getProperty("SUPL_HOST");
+            String portString = mProperties.getProperty("SUPL_PORT");
+            if (mSuplServerHost != null && portString != null) {
+                try {
+                    mSuplServerPort = Integer.parseInt(portString);
+                } catch (NumberFormatException e) {
+                    Log.e(TAG, "unable to parse SUPL_PORT: " + portString);
+                }
+            }
+
+            mC2KServerHost = mProperties.getProperty("C2K_HOST");
+            portString = mProperties.getProperty("C2K_PORT");
+            if (mC2KServerHost != null && portString != null) {
+                try {
+                    mC2KServerPort = Integer.parseInt(portString);
+                } catch (NumberFormatException e) {
+                    Log.e(TAG, "unable to parse C2K_PORT: " + portString);
+                }
+            }
+        } catch (IOException e) {
+            Log.w(TAG, "Could not open GPS configuration file " + PROPERTIES_FILE);
+        }
+
+        // wait until we are fully initialized before returning
+        mThread = new GpsLocationProviderThread();
+        mThread.start();
+        while (true) {
+            try {
+                mInitializedLatch.await();
+                break;
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    private void initialize() {
+        // register our receiver on our thread rather than the main thread
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(ALARM_WAKEUP);
+        intentFilter.addAction(ALARM_TIMEOUT);
+        mContext.registerReceiver(mBroadcastReciever, intentFilter);
+    }
+
+    /**
+     * Returns the name of this provider.
+     */
+    public String getName() {
+        return LocationManager.GPS_PROVIDER;
+    }
+
+    /**
+     * Returns true if the provider requires access to a
+     * data network (e.g., the Internet), false otherwise.
+     */
+    public boolean requiresNetwork() {
+        return true;
+    }
+
+    public void updateNetworkState(int state, NetworkInfo info) {
+        mHandler.removeMessages(UPDATE_NETWORK_STATE);
+        Message m = Message.obtain(mHandler, UPDATE_NETWORK_STATE);
+        m.arg1 = state;
+        m.obj = info;
+        mHandler.sendMessage(m);
+    }
+
+    private void handleUpdateNetworkState(int state, NetworkInfo info) {
+        mNetworkAvailable = (state == LocationProvider.AVAILABLE);
+
+        if (DEBUG) {
+            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) {
+            if (mInjectNtpTimePending) {
+                mHandler.removeMessages(INJECT_NTP_TIME);
+                mHandler.sendMessage(Message.obtain(mHandler, INJECT_NTP_TIME));
+            }
+            if (mDownloadXtraDataPending) {
+                mHandler.removeMessages(DOWNLOAD_XTRA_DATA);
+                mHandler.sendMessage(Message.obtain(mHandler, DOWNLOAD_XTRA_DATA));
+            }
+        }
+    }
+
+    private void handleInjectNtpTime() {
+        if (!mNetworkAvailable) {
+            // try again when network is up
+            mInjectNtpTimePending = true;
+            return;
+        }
+        mInjectNtpTimePending = false;
+
+        SntpClient client = new SntpClient();
+        long delay;
+
+        if (client.requestTime(mNtpServer, 10000)) {
+            long time = client.getNtpTime();
+            long timeReference = client.getNtpTimeReference();
+            int certainty = (int)(client.getRoundTripTime()/2);
+            long now = System.currentTimeMillis();
+            long systemTimeOffset = time - now;
+
+            Log.d(TAG, "NTP server returned: "
+                    + time + " (" + new Date(time)
+                    + ") reference: " + timeReference
+                    + " certainty: " + certainty
+                    + " system time offset: " + systemTimeOffset);
+
+            // sanity check NTP time and do not use if it is too far from system time
+            if (systemTimeOffset < 0) {
+                systemTimeOffset = -systemTimeOffset;
+            }
+            if (systemTimeOffset < MAX_NTP_SYSTEM_TIME_OFFSET) {
+                native_inject_time(time, timeReference, certainty);
+            } else {
+                Log.e(TAG, "NTP time differs from system time by " + systemTimeOffset
+                        + "ms.  Ignoring.");
+            }
+            delay = NTP_INTERVAL;
+        } else {
+            if (DEBUG) Log.d(TAG, "requestTime failed");
+            delay = RETRY_INTERVAL;
+        }
+
+        // send delayed message for next NTP injection
+        mHandler.removeMessages(INJECT_NTP_TIME);
+        mHandler.sendMessageDelayed(Message.obtain(mHandler, INJECT_NTP_TIME), delay);
+    }
+
+    private void handleDownloadXtraData() {
+        if (!mDownloadXtraDataPending) {
+            // try again when network is up
+            mDownloadXtraDataPending = true;
+            return;
+        }
+        mDownloadXtraDataPending = false;
+
+
+        GpsXtraDownloader xtraDownloader = new GpsXtraDownloader(mContext, mProperties);
+        byte[] data = xtraDownloader.downloadXtraData();
+        if (data != null) {
+            if (DEBUG) {
+                Log.d(TAG, "calling native_inject_xtra_data");
+            }
+            native_inject_xtra_data(data, data.length);
+        } else {
+            // try again later
+            mHandler.removeMessages(DOWNLOAD_XTRA_DATA);
+            mHandler.sendMessageDelayed(Message.obtain(mHandler, DOWNLOAD_XTRA_DATA), RETRY_INTERVAL);
+        }
+    }
+
+    /**
+     * This is called to inform us when another location provider returns a location.
+     * Someday we might use this for network location injection to aid the GPS
+     */
+    public void updateLocation(Location location) {
+        mHandler.removeMessages(UPDATE_LOCATION);
+        Message m = Message.obtain(mHandler, UPDATE_LOCATION);
+        m.obj = location;
+        mHandler.sendMessage(m);
+    }
+
+    private void handleUpdateLocation(Location location) {
+        if (location.hasAccuracy()) {
+            native_inject_location(location.getLatitude(), location.getLongitude(),
+                    location.getAccuracy());
+        }
+    }
+
+    /**
+     * Returns true if the provider requires access to a
+     * satellite-based positioning system (e.g., GPS), false
+     * otherwise.
+     */
+    public boolean requiresSatellite() {
+        return true;
+    }
+
+    /**
+     * Returns true if the provider requires access to an appropriate
+     * cellular network (e.g., to make use of cell tower IDs), false
+     * otherwise.
+     */
+    public boolean requiresCell() {
+        return false;
+    }
+
+    /**
+     * Returns true if the use of this provider may result in a
+     * monetary charge to the user, false if use is free.  It is up to
+     * each provider to give accurate information.
+     */
+    public boolean hasMonetaryCost() {
+        return false;
+    }
+
+    /**
+     * Returns true if the provider is able to provide altitude
+     * information, false otherwise.  A provider that reports altitude
+     * under most circumstances but may occassionally not report it
+     * should return true.
+     */
+    public boolean supportsAltitude() {
+        return true;
+    }
+
+    /**
+     * Returns true if the provider is able to provide speed
+     * information, false otherwise.  A provider that reports speed
+     * under most circumstances but may occassionally not report it
+     * should return true.
+     */
+    public boolean supportsSpeed() {
+        return true;
+    }
+
+    /**
+     * Returns true if the provider is able to provide bearing
+     * information, false otherwise.  A provider that reports bearing
+     * under most circumstances but may occassionally not report it
+     * should return true.
+     */
+    public boolean supportsBearing() {
+        return true;
+    }
+
+    /**
+     * Returns the power requirement for this provider.
+     *
+     * @return the power requirement for this provider, as one of the
+     * constants Criteria.POWER_REQUIREMENT_*.
+     */
+    public int getPowerRequirement() {
+        return Criteria.POWER_HIGH;
+    }
+
+    /**
+     * Returns the horizontal accuracy of this provider
+     *
+     * @return the accuracy of location from this provider, as one
+     * of the constants Criteria.ACCURACY_*.
+     */
+    public int getAccuracy() {
+        return Criteria.ACCURACY_FINE;
+    }
+
+    /**
+     * Enables this provider.  When enabled, calls to getStatus()
+     * must be handled.  Hardware may be started up
+     * when the provider is enabled.
+     */
+    public void enable() {
+        synchronized (mHandler) {
+            mHandler.removeMessages(ENABLE);
+            Message m = Message.obtain(mHandler, ENABLE);
+            m.arg1 = 1;
+            mHandler.sendMessage(m);
+        }
+    }
+
+    private void handleEnable() {
+        if (DEBUG) Log.d(TAG, "handleEnable");
+        if (mEnabled) return;
+        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();
+        } else {
+            Log.w(TAG, "Failed to enable location provider");
+        }
+    }
+
+    /**
+     * Disables this provider.  When disabled, calls to getStatus()
+     * need not be handled.  Hardware may be shut
+     * down while the provider is disabled.
+     */
+    public void disable() {
+        synchronized (mHandler) {
+            mHandler.removeMessages(ENABLE);
+            Message m = Message.obtain(mHandler, ENABLE);
+            m.arg1 = 0;
+            mHandler.sendMessage(m);
+        }
+    }
+
+    private void handleDisable() {
+        if (DEBUG) Log.d(TAG, "handleDisable");
+        if (!mEnabled) return;
+
+        mEnabled = false;
+        stopNavigating();
+        native_disable();
+
+        // make sure our event thread exits
+        if (mEventThread != null) {
+            try {
+                mEventThread.join();
+            } catch (InterruptedException e) {
+                Log.w(TAG, "InterruptedException when joining mEventThread");
+            }
+            mEventThread = null;
+        }
+
+        // do this before releasing wakelock
+        native_cleanup();
+
+        // The GpsEventThread does not wait for the GPS to shutdown
+        // so we need to report the GPS_STATUS_ENGINE_OFF event here
+        if (mNavigating) {
+            reportStatus(GPS_STATUS_SESSION_END);
+        }
+        if (mEngineOn) {
+            reportStatus(GPS_STATUS_ENGINE_OFF);
+        }
+    }
+
+    public boolean isEnabled() {
+        return mEnabled;
+    }
+
+    public int getStatus(Bundle extras) {
+        if (extras != null) {
+            extras.putInt("satellites", mSvCount);
+        }
+        return mStatus;
+    }
+
+    private void updateStatus(int status, int svCount) {
+        if (status != mStatus || svCount != mSvCount) {
+            mStatus = status;
+            mSvCount = svCount;
+            mLocationExtras.putInt("satellites", svCount);
+            mStatusUpdateTime = SystemClock.elapsedRealtime();
+        }
+    }
+
+    public long getStatusUpdateTime() {
+        return mStatusUpdateTime;
+    }
+
+    public void enableLocationTracking(boolean enable) {
+        synchronized (mHandler) {
+            mHandler.removeMessages(ENABLE_TRACKING);
+            Message m = Message.obtain(mHandler, ENABLE_TRACKING);
+            m.arg1 = (enable ? 1 : 0);
+            mHandler.sendMessage(m);
+        }
+    }
+
+    private void handleEnableLocationTracking(boolean enable) {
+        if (enable) {
+            mTTFF = 0;
+            mLastFixTime = 0;
+            startNavigating();
+        } else {
+            mAlarmManager.cancel(mWakeupIntent);
+            mAlarmManager.cancel(mTimeoutIntent);
+            stopNavigating();
+        }
+    }
+
+    public void setMinTime(long minTime) {
+        if (DEBUG) Log.d(TAG, "setMinTime " + minTime);
+        
+        if (minTime >= 0) {
+            int interval = (int)(minTime/1000);
+            if (interval < 1) {
+                interval = 1;
+            }
+            mFixInterval = interval;
+        }
+    }
+
+    public String getInternalState() {
+        return native_get_internal_state();
+    }
+
+    private final class Listener implements IBinder.DeathRecipient {
+        final IGpsStatusListener mListener;
+        
+        int mSensors = 0;
+        
+        Listener(IGpsStatusListener listener) {
+            mListener = listener;
+        }
+        
+        public void binderDied() {
+            if (DEBUG) Log.d(TAG, "GPS status listener died");
+
+            synchronized(mListeners) {
+                mListeners.remove(this);
+            }
+            if (mListener != null) {
+                mListener.asBinder().unlinkToDeath(this, 0);
+            }
+        }
+    }
+
+    public void addListener(int uid) {
+        Message m = Message.obtain(mHandler, ADD_LISTENER);
+        m.arg1 = uid;
+        mHandler.sendMessage(m);
+    }
+
+    private void handleAddListener(int uid) {
+        synchronized(mListeners) {
+            if (mClientUids.indexOfKey(uid) >= 0) {
+                // Shouldn't be here -- already have this uid.
+                Log.w(TAG, "Duplicate add listener for uid " + uid);
+                return;
+            }
+            mClientUids.put(uid, 0);
+            if (mNavigating) {
+                try {
+                    mBatteryStats.noteStartGps(uid);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "RemoteException in addListener");
+                }
+            }
+        }
+    }
+
+    public void removeListener(int uid) {
+        Message m = Message.obtain(mHandler, REMOVE_LISTENER);
+        m.arg1 = uid;
+        mHandler.sendMessage(m);
+    }
+
+    private void handleRemoveListener(int uid) {
+        synchronized(mListeners) {
+            if (mClientUids.indexOfKey(uid) < 0) {
+                // Shouldn't be here -- don't have this uid.
+                Log.w(TAG, "Unneeded remove listener for uid " + uid);
+                return;
+            }
+            mClientUids.delete(uid);
+            if (mNavigating) {
+                try {
+                    mBatteryStats.noteStopGps(uid);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "RemoteException in removeListener");
+                }
+            }
+        }
+    }
+
+    public boolean sendExtraCommand(String command, Bundle extras) {
+        
+        if ("delete_aiding_data".equals(command)) {
+            return deleteAidingData(extras);
+        }
+        if ("force_time_injection".equals(command)) {
+            mHandler.removeMessages(INJECT_NTP_TIME);
+            mHandler.sendMessage(Message.obtain(mHandler, INJECT_NTP_TIME));
+            return true;
+        }
+        if ("force_xtra_injection".equals(command)) {
+            if (native_supports_xtra()) {
+                xtraDownloadRequest();
+                return true;
+            }
+            return false;
+        }
+        
+        Log.w(TAG, "sendExtraCommand: unknown command " + command);
+        return false;
+    }
+
+    private boolean deleteAidingData(Bundle extras) {
+        int flags;
+
+        if (extras == null) {
+            flags = GPS_DELETE_ALL;
+        } else {
+            flags = 0;
+            if (extras.getBoolean("ephemeris")) flags |= GPS_DELETE_EPHEMERIS;
+            if (extras.getBoolean("almanac")) flags |= GPS_DELETE_ALMANAC;
+            if (extras.getBoolean("position")) flags |= GPS_DELETE_POSITION;
+            if (extras.getBoolean("time")) flags |= GPS_DELETE_TIME;
+            if (extras.getBoolean("iono")) flags |= GPS_DELETE_IONO;
+            if (extras.getBoolean("utc")) flags |= GPS_DELETE_UTC;
+            if (extras.getBoolean("health")) flags |= GPS_DELETE_HEALTH;
+            if (extras.getBoolean("svdir")) flags |= GPS_DELETE_SVDIR;
+            if (extras.getBoolean("svsteer")) flags |= GPS_DELETE_SVSTEER;
+            if (extras.getBoolean("sadata")) flags |= GPS_DELETE_SADATA;
+            if (extras.getBoolean("rti")) flags |= GPS_DELETE_RTI;
+            if (extras.getBoolean("celldb-info")) flags |= GPS_DELETE_CELLDB_INFO;
+            if (extras.getBoolean("all")) flags |= GPS_DELETE_ALL;
+        }
+
+        if (flags != 0) {
+            native_delete_aiding_data(flags);
+            return true;
+        }
+
+        return false;
+    }
+
+    private void startNavigating() {
+        if (!mStarted) {
+            if (DEBUG) Log.d(TAG, "startNavigating");
+            mStarted = true;
+            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, 1)) {
+                mStarted = false;
+                Log.e(TAG, "native_start failed in startNavigating()");
+                return;
+            }
+
+            // reset SV count to zero
+            updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, 0);
+            mFixCount = 0;
+            mFixRequestTime = System.currentTimeMillis();
+            // set timer to give up if we do not receive a fix within NO_FIX_TIMEOUT
+            // and our fix interval is not short
+            if (mFixInterval >= NO_FIX_TIMEOUT) {
+                mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                        SystemClock.elapsedRealtime() + NO_FIX_TIMEOUT * 1000, mTimeoutIntent);
+            }
+        }
+    }
+
+    private void stopNavigating() {
+        if (DEBUG) Log.d(TAG, "stopNavigating");
+        if (mStarted) {
+            mStarted = false;
+            native_stop();
+            mTTFF = 0;
+            mLastFixTime = 0;
+            mLocationFlags = LOCATION_INVALID;
+
+            // reset SV count to zero
+            updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, 0);
+        }
+    }
+
+    private void hibernate() {
+        // stop GPS until our next fix interval arrives
+        stopNavigating();
+        mFixCount = 0;
+        mAlarmManager.cancel(mTimeoutIntent);
+        mAlarmManager.cancel(mWakeupIntent);
+        long now = SystemClock.elapsedRealtime();
+        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                SystemClock.elapsedRealtime() + mFixInterval * 1000, mWakeupIntent);
+    }
+
+    /**
+     * called from native code to update our position.
+     */
+    private void reportLocation(int flags, double latitude, double longitude, double altitude,
+            float speed, float bearing, float accuracy, long timestamp) {
+        if (VERBOSE) Log.v(TAG, "reportLocation lat: " + latitude + " long: " + longitude +
+                " timestamp: " + timestamp);
+
+        mLastFixTime = System.currentTimeMillis();
+        // report time to first fix
+        if (mTTFF == 0 && (flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) {
+            mTTFF = (int)(mLastFixTime - mFixRequestTime);
+            if (DEBUG) Log.d(TAG, "TTFF: " + mTTFF);
+
+            // notify status listeners
+            synchronized(mListeners) {
+                int size = mListeners.size();
+                for (int i = 0; i < size; i++) {
+                    Listener listener = mListeners.get(i);
+                    try {
+                        listener.mListener.onFirstFix(mTTFF); 
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "RemoteException in stopNavigating");
+                        mListeners.remove(listener);
+                        // adjust for size of list changing
+                        size--;
+                    }
+                }
+            }
+        }
+
+        synchronized (mLocation) {
+            mLocationFlags = flags;
+            if ((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) {
+                mLocation.setLatitude(latitude);
+                mLocation.setLongitude(longitude);
+                mLocation.setTime(timestamp);
+            }
+            if ((flags & LOCATION_HAS_ALTITUDE) == LOCATION_HAS_ALTITUDE) {
+                mLocation.setAltitude(altitude);
+            } else {
+                mLocation.removeAltitude();
+            }
+            if ((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) {
+                mLocation.setSpeed(speed);
+            } else {
+                mLocation.removeSpeed();
+            }
+            if ((flags & LOCATION_HAS_BEARING) == LOCATION_HAS_BEARING) {
+                mLocation.setBearing(bearing);
+            } else {
+                mLocation.removeBearing();
+            }
+            if ((flags & LOCATION_HAS_ACCURACY) == LOCATION_HAS_ACCURACY) {
+                mLocation.setAccuracy(accuracy);
+            } else {
+                mLocation.removeAccuracy();
+            }
+
+            try {
+                mLocationManager.reportLocation(mLocation, false);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException calling reportLocation");
+            }
+        }
+
+        if (mStarted && mStatus != LocationProvider.AVAILABLE) {
+            // we still want to time out if we do not receive MIN_FIX_COUNT
+            // within the time out and we are requesting infrequent fixes
+            if (mFixInterval < NO_FIX_TIMEOUT) {
+                mAlarmManager.cancel(mTimeoutIntent);
+            }
+
+            // send an intent to notify that the GPS is receiving fixes.
+            Intent intent = new Intent(LocationManager.GPS_FIX_CHANGE_ACTION);
+            intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, true);
+            mContext.sendBroadcast(intent);
+            updateStatus(LocationProvider.AVAILABLE, mSvCount);
+        }
+
+        if (mFixCount++ >= MIN_FIX_COUNT && mFixInterval > 1) {
+            if (DEBUG) Log.d(TAG, "exceeded MIN_FIX_COUNT");
+            hibernate();
+        }
+   }
+
+    /**
+     * called from native code to update our status
+     */
+    private void reportStatus(int status) {
+        if (VERBOSE) Log.v(TAG, "reportStatus status: " + status);
+
+        synchronized(mListeners) {
+            boolean wasNavigating = mNavigating;
+
+            switch (status) {
+                case GPS_STATUS_SESSION_BEGIN:
+                    mNavigating = true;
+                    mEngineOn = true;
+                    break;
+                case GPS_STATUS_SESSION_END:
+                    mNavigating = false;
+                    break;
+                case GPS_STATUS_ENGINE_ON:
+                    mEngineOn = true;
+                    break;
+                case GPS_STATUS_ENGINE_OFF:
+                    mEngineOn = false;
+                    mNavigating = false;
+                    break;
+            }
+
+            // beware, the events can come out of order
+            if ((mNavigating || mEngineOn) && !mWakeLock.isHeld()) {
+                if (DEBUG) Log.d(TAG, "Acquiring wakelock");
+                 mWakeLock.acquire();
+            }
+
+            if (wasNavigating != mNavigating) {
+                int size = mListeners.size();
+                for (int i = 0; i < size; i++) {
+                    Listener listener = mListeners.get(i);
+                    try {
+                        if (mNavigating) {
+                            listener.mListener.onGpsStarted();
+                        } else {
+                            listener.mListener.onGpsStopped();
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "RemoteException in reportStatus");
+                        mListeners.remove(listener);
+                        // adjust for size of list changing
+                        size--;
+                    }
+                }
+
+                try {
+                    // update battery stats
+                    for (int i=mClientUids.size() - 1; i >= 0; i--) {
+                        int uid = mClientUids.keyAt(i);
+                        if (mNavigating) {
+                            mBatteryStats.noteStartGps(uid);
+                        } else {
+                            mBatteryStats.noteStopGps(uid);
+                        }
+                    }
+                } catch (RemoteException e) {
+                    Log.w(TAG, "RemoteException in reportStatus");
+                }
+
+                // send an intent to notify that the GPS has been enabled or disabled.
+                Intent intent = new Intent(LocationManager.GPS_ENABLED_CHANGE_ACTION);
+                intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, mNavigating);
+                mContext.sendBroadcast(intent);
+            }
+
+            // beware, the events can come out of order
+            if (!mNavigating && !mEngineOn && mWakeLock.isHeld()) {
+                if (DEBUG) Log.d(TAG, "Releasing wakelock");
+                mWakeLock.release();
+            }
+        }
+    }
+
+    /**
+     * called from native code to update SV info
+     */
+    private void reportSvStatus() {
+
+        int svCount = native_read_sv_status(mSvs, mSnrs, mSvElevations, mSvAzimuths, mSvMasks);
+        
+        synchronized(mListeners) {
+            int size = mListeners.size();
+            for (int i = 0; i < size; i++) {
+                Listener listener = mListeners.get(i);
+                try {
+                    listener.mListener.onSvStatusChanged(svCount, mSvs, mSnrs, 
+                            mSvElevations, mSvAzimuths, mSvMasks[EPHEMERIS_MASK], 
+                            mSvMasks[ALMANAC_MASK], mSvMasks[USED_FOR_FIX_MASK]); 
+                } catch (RemoteException e) {
+                    Log.w(TAG, "RemoteException in reportSvInfo");
+                    mListeners.remove(listener);
+                    // adjust for size of list changing
+                    size--;
+                }
+            }
+        }
+
+        if (VERBOSE) {
+            Log.v(TAG, "SV count: " + svCount +
+                    " ephemerisMask: " + Integer.toHexString(mSvMasks[EPHEMERIS_MASK]) +
+                    " almanacMask: " + Integer.toHexString(mSvMasks[ALMANAC_MASK]));
+            for (int i = 0; i < svCount; i++) {
+                Log.v(TAG, "sv: " + mSvs[i] +
+                        " snr: " + (float)mSnrs[i]/10 +
+                        " elev: " + mSvElevations[i] +
+                        " azimuth: " + mSvAzimuths[i] +
+                        ((mSvMasks[EPHEMERIS_MASK] & (1 << (mSvs[i] - 1))) == 0 ? "  " : " E") +
+                        ((mSvMasks[ALMANAC_MASK] & (1 << (mSvs[i] - 1))) == 0 ? "  " : " A") +
+                        ((mSvMasks[USED_FOR_FIX_MASK] & (1 << (mSvs[i] - 1))) == 0 ? "" : "U"));
+            }
+        }
+
+        updateStatus(mStatus, svCount);
+
+        if (mNavigating && mStatus == LocationProvider.AVAILABLE && mLastFixTime > 0 &&
+            System.currentTimeMillis() - mLastFixTime > RECENT_FIX_TIMEOUT * 1000) {
+            // send an intent to notify that the GPS is no longer receiving fixes.
+            Intent intent = new Intent(LocationManager.GPS_FIX_CHANGE_ACTION);
+            intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, false);
+            mContext.sendBroadcast(intent);
+            updateStatus(LocationProvider.TEMPORARILY_UNAVAILABLE, mSvCount);
+        }
+    }
+
+    /**
+     * called from native code to update AGPS status
+     */
+    private void reportAGpsStatus(int type, int status) {
+        switch (status) {
+            case GPS_REQUEST_AGPS_DATA_CONN:
+                 int result = mConnMgr.startUsingNetworkFeature(
+                        ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_SUPL);
+                if (result == Phone.APN_ALREADY_ACTIVE) {
+                    if (mAGpsApn != null) {
+                        native_agps_data_conn_open(mAGpsApn);
+                        mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPEN;
+                    } else {
+                        Log.e(TAG, "mAGpsApn not set when receiving Phone.APN_ALREADY_ACTIVE");
+                        native_agps_data_conn_failed();
+                    }
+                } else if (result == Phone.APN_REQUEST_STARTED) {
+                    mAGpsDataConnectionState = AGPS_DATA_CONNECTION_OPENING;
+                } else {
+                    native_agps_data_conn_failed();
+                }
+                break;
+            case GPS_RELEASE_AGPS_DATA_CONN:
+                if (mAGpsDataConnectionState != AGPS_DATA_CONNECTION_CLOSED) {
+                    mConnMgr.stopUsingNetworkFeature(
+                            ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_SUPL);
+                    native_agps_data_conn_closed();
+                    mAGpsDataConnectionState = AGPS_DATA_CONNECTION_CLOSED;
+                }
+                break;
+            case GPS_AGPS_DATA_CONNECTED:
+                // Log.d(TAG, "GPS_AGPS_DATA_CONNECTED");
+                break;
+            case GPS_AGPS_DATA_CONN_DONE:
+                // Log.d(TAG, "GPS_AGPS_DATA_CONN_DONE");
+                break;
+            case GPS_AGPS_DATA_CONN_FAILED:
+                // Log.d(TAG, "GPS_AGPS_DATA_CONN_FAILED");
+                break;
+        }
+    }
+
+    /**
+     * 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--;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * called from native code to request XTRA data
+     */
+    private void xtraDownloadRequest() {
+        if (DEBUG) Log.d(TAG, "xtraDownloadRequest");
+        mHandler.removeMessages(DOWNLOAD_XTRA_DATA);
+        mHandler.sendMessage(Message.obtain(mHandler, DOWNLOAD_XTRA_DATA));
+    }
+
+    //=============================================================
+    // 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 (DEBUG) 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<Object, Object> ent : extraProp.entrySet())
+		{
+			bundle.putString((String) ent.getKey(), (String) ent.getValue());
+		}		
+		
+		notification.extras = bundle;
+		
+		mNIHandler.handleNiNotification(notification);		
+	}
+
+    // this thread is used to receive events from the native code.
+    // native_wait_for_event() will callback to us via reportLocation(), reportStatus(), etc.
+    // this is necessary because native code cannot call Java on a thread that the JVM does
+    // not know about.
+    private final class GpsEventThread extends Thread {
+
+        public GpsEventThread() {
+            super("GpsEventThread");
+        }
+
+        public void run() {
+            if (DEBUG) Log.d(TAG, "GpsEventThread starting");
+            // Exit as soon as disable() is called instead of waiting for the GPS to stop.
+            while (mEnabled) {
+                // this will wait for an event from the GPS,
+                // which will be reported via reportLocation or reportStatus
+                native_wait_for_event();
+            }
+            if (DEBUG) Log.d(TAG, "GpsEventThread exiting");
+        }
+    }
+
+    private final class ProviderHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg)
+        {
+            switch (msg.what) {
+                case ENABLE:
+                    if (msg.arg1 == 1) {
+                        handleEnable();
+                    } else {
+                        handleDisable();
+                    }
+                    break;
+                case ENABLE_TRACKING:
+                    handleEnableLocationTracking(msg.arg1 == 1);
+                    break;
+                case UPDATE_NETWORK_STATE:
+                    handleUpdateNetworkState(msg.arg1, (NetworkInfo)msg.obj);
+                    break;
+                case INJECT_NTP_TIME:
+                    handleInjectNtpTime();
+                    break;
+                case DOWNLOAD_XTRA_DATA:
+                    if (native_supports_xtra()) {
+                        handleDownloadXtraData();
+                    }
+                    break;
+                case UPDATE_LOCATION:
+                    handleUpdateLocation((Location)msg.obj);
+                    break;
+                case ADD_LISTENER:
+                    handleAddListener(msg.arg1);
+                    break;
+                case REMOVE_LISTENER:
+                    handleRemoveListener(msg.arg1);
+                    break;
+            }
+        }
+    };
+
+    private final class GpsLocationProviderThread extends Thread {
+
+        public GpsLocationProviderThread() {
+            super("GpsLocationProvider");
+        }
+
+        public void run() {
+            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+            initialize();
+            Looper.prepare();
+            mHandler = new ProviderHandler();
+            // signal when we are initialized and ready to go
+            mInitializedLatch.countDown();
+            Looper.loop();
+        }
+    }
+
+    // for GPS SV statistics
+    private static final int MAX_SVS = 32;
+    private static final int EPHEMERIS_MASK = 0;
+    private static final int ALMANAC_MASK = 1;
+    private static final int USED_FOR_FIX_MASK = 2;
+
+    // preallocated arrays, to avoid memory allocation in reportStatus()
+    private int mSvs[] = new int[MAX_SVS];
+    private float mSnrs[] = new float[MAX_SVS];
+    private float mSvElevations[] = new float[MAX_SVS];
+    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();
+    private static native boolean native_is_supported();
+
+    private native boolean native_init();
+    private native void native_disable();
+    private native void native_cleanup();
+    private native boolean native_start(int positionMode, boolean singleFix, int fixInterval);
+    private native boolean native_stop();
+    private native void native_set_fix_frequency(int fixFrequency);
+    private native void native_delete_aiding_data(int flags);
+    private native void native_wait_for_event();
+    // returns number of SVs
+    // 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
+    private native void native_inject_time(long time, long timeReference, int uncertainty);
+    private native boolean native_supports_xtra();
+    private native void native_inject_xtra_data(byte[] data, int length);
+
+    // DEBUG Support
+    private native String native_get_internal_state();
+
+    // AGPS Support
+    private native void native_agps_data_conn_open(String apn);
+    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/services/java/com/android/server/location/GpsXtraDownloader.java b/services/java/com/android/server/location/GpsXtraDownloader.java
new file mode 100644
index 0000000..bc96980
--- /dev/null
+++ b/services/java/com/android/server/location/GpsXtraDownloader.java
@@ -0,0 +1,171 @@
+/*
+ * 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.server.location;
+
+import android.content.Context;
+import android.net.Proxy;
+import android.net.http.AndroidHttpClient;
+import android.util.Config;
+import android.util.Log;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.conn.params.ConnRouteParams;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.util.Properties;
+import java.util.Random;
+
+/**
+ * A class for downloading GPS XTRA data.
+ *
+ * {@hide}
+ */
+public class GpsXtraDownloader {
+
+    private static final String TAG = "GpsXtraDownloader";
+    
+    private Context mContext;
+    private String[] mXtraServers;
+    // to load balance our server requests
+    private int mNextServerIndex;
+
+    GpsXtraDownloader(Context context, Properties properties) {
+        mContext = context;
+
+        // read XTRA servers from the Properties object
+        int count = 0;
+        String server1 = properties.getProperty("XTRA_SERVER_1");
+        String server2 = properties.getProperty("XTRA_SERVER_2");
+        String server3 = properties.getProperty("XTRA_SERVER_3");
+        if (server1 != null) count++;
+        if (server2 != null) count++;
+        if (server3 != null) count++;
+        
+        if (count == 0) {
+            Log.e(TAG, "No XTRA servers were specified in the GPS configuration");
+            return;
+        } else {
+            mXtraServers = new String[count];
+            count = 0;
+            if (server1 != null) mXtraServers[count++] = server1;
+            if (server2 != null) mXtraServers[count++] = server2;
+            if (server3 != null) mXtraServers[count++] = server3;
+
+            // randomize first server
+            Random random = new Random();
+            mNextServerIndex = random.nextInt(count);
+        }       
+    }
+
+    byte[] downloadXtraData() {
+        String proxyHost = Proxy.getHost(mContext);
+        int proxyPort = Proxy.getPort(mContext);
+        boolean useProxy = (proxyHost != null && proxyPort != -1);
+        byte[] result = null;
+        int startIndex = mNextServerIndex;
+
+        if (mXtraServers == null) {
+            return null;
+        }
+
+        // load balance our requests among the available servers
+        while (result == null) {
+            result = doDownload(mXtraServers[mNextServerIndex], useProxy, proxyHost, proxyPort);
+            
+            // increment mNextServerIndex and wrap around if necessary
+            mNextServerIndex++;
+            if (mNextServerIndex == mXtraServers.length) {
+                mNextServerIndex = 0;
+            }
+            // break if we have tried all the servers
+            if (mNextServerIndex == startIndex) break;
+        }
+    
+        return result;
+    }
+
+    protected static byte[] doDownload(String url, boolean isProxySet, 
+            String proxyHost, int proxyPort) {
+        if (Config.LOGD) Log.d(TAG, "Downloading XTRA data from " + url);
+
+        AndroidHttpClient client = null;
+        try {
+            client = AndroidHttpClient.newInstance("Android");
+            HttpUriRequest req = new HttpGet(url);
+
+            if (isProxySet) {
+                HttpHost proxy = new HttpHost(proxyHost, proxyPort);
+                ConnRouteParams.setDefaultProxy(req.getParams(), proxy);
+            }
+
+            req.addHeader(
+                    "Accept",
+                    "*/*, application/vnd.wap.mms-message, application/vnd.wap.sic");
+
+            req.addHeader(
+                    "x-wap-profile",
+                    "http://www.openmobilealliance.org/tech/profiles/UAPROF/ccppschema-20021212#");
+
+            HttpResponse response = client.execute(req);
+            StatusLine status = response.getStatusLine();
+            if (status.getStatusCode() != 200) { // HTTP 200 is success.
+                if (Config.LOGD) Log.d(TAG, "HTTP error: " + status.getReasonPhrase());
+                return null;
+            }
+
+            HttpEntity entity = response.getEntity();
+            byte[] body = null;
+            if (entity != null) {
+                try {
+                    if (entity.getContentLength() > 0) {
+                        body = new byte[(int) entity.getContentLength()];
+                        DataInputStream dis = new DataInputStream(entity.getContent());
+                        try {
+                            dis.readFully(body);
+                        } finally {
+                            try {
+                                dis.close();
+                            } catch (IOException e) {
+                                Log.e(TAG, "Unexpected IOException.", e);
+                            }
+                        }
+                    }
+                } finally {
+                    if (entity != null) {
+                        entity.consumeContent();
+                    }
+                }
+            }
+            return body;
+        } catch (Exception e) {
+            if (Config.LOGD) Log.d(TAG, "error " + e);
+        } finally {
+            if (client != null) {
+                client.close();
+            }
+        }
+        return null;
+    }
+
+}
diff --git a/services/java/com/android/server/location/LocationProviderInterface.java b/services/java/com/android/server/location/LocationProviderInterface.java
new file mode 100644
index 0000000..a472143
--- /dev/null
+++ b/services/java/com/android/server/location/LocationProviderInterface.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2010 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.server.location;
+
+import android.location.Location;
+import android.net.NetworkInfo;
+import android.os.Bundle;
+
+/**
+ * Location Manager's interface for location providers.
+ *
+ * {@hide}
+ */
+public interface LocationProviderInterface {
+    String getName();
+    boolean requiresNetwork();
+    boolean requiresSatellite();
+    boolean requiresCell();
+    boolean hasMonetaryCost();
+    boolean supportsAltitude();
+    boolean supportsSpeed();
+    boolean supportsBearing();
+    int getPowerRequirement();
+    int getAccuracy();
+    boolean isEnabled();
+    void enable();
+    void disable();
+    int getStatus(Bundle extras);
+    long getStatusUpdateTime();
+    void enableLocationTracking(boolean enable);
+    String getInternalState();
+    void setMinTime(long minTime);
+    void updateNetworkState(int state, NetworkInfo info);
+    void updateLocation(Location location);
+    boolean sendExtraCommand(String command, Bundle extras);
+    void addListener(int uid);
+    void removeListener(int uid);
+}
diff --git a/services/java/com/android/server/location/LocationProviderProxy.java b/services/java/com/android/server/location/LocationProviderProxy.java
new file mode 100644
index 0000000..3e118f9
--- /dev/null
+++ b/services/java/com/android/server/location/LocationProviderProxy.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2009 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.server.location;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.location.ILocationProvider;
+import android.location.Location;
+import android.net.NetworkInfo;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.internal.location.DummyLocationProvider;
+
+/**
+ * A class for proxying location providers implemented as services.
+ *
+ * {@hide}
+ */
+public class LocationProviderProxy implements LocationProviderInterface {
+
+    private static final String TAG = "LocationProviderProxy";
+
+    private final Context mContext;
+    private final String mName;
+    private ILocationProvider mProvider;
+    private Handler mHandler;
+    private final Connection mServiceConnection = new Connection();
+
+    // cached values set by the location manager
+    private boolean mLocationTracking = false;
+    private boolean mEnabled = false;
+    private long mMinTime = -1;
+    private int mNetworkState;
+    private NetworkInfo mNetworkInfo;
+
+    // for caching requiresNetwork, requiresSatellite, etc.
+    private DummyLocationProvider mCachedAttributes;
+
+    // constructor for proxying location providers implemented in a separate service
+    public LocationProviderProxy(Context context, String name, String serviceName,
+            Handler handler) {
+        mContext = context;
+        mName = name;
+        mHandler = handler;
+        mContext.bindService(new Intent(serviceName), mServiceConnection, Context.BIND_AUTO_CREATE);
+    }
+
+    private class Connection implements ServiceConnection {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            Log.d(TAG, "LocationProviderProxy.onServiceConnected " + className);
+            synchronized (this) {
+                mProvider = ILocationProvider.Stub.asInterface(service);
+                if (mProvider != null) {
+                    mHandler.post(mServiceConnectedTask);
+                }
+            }
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            Log.d(TAG, "LocationProviderProxy.onServiceDisconnected " + className);
+            synchronized (this) {
+                mProvider = null;
+            }
+        }
+    }
+
+    private Runnable mServiceConnectedTask = new Runnable() {
+        public void run() {
+            ILocationProvider provider;
+            synchronized (mServiceConnection) {
+                provider = mProvider;
+                if (provider == null) {
+                    return;
+                }
+            }
+
+            if (mCachedAttributes == null) {
+                try {
+                    mCachedAttributes = new DummyLocationProvider(mName);
+                    mCachedAttributes.setRequiresNetwork(provider.requiresNetwork());
+                    mCachedAttributes.setRequiresSatellite(provider.requiresSatellite());
+                    mCachedAttributes.setRequiresCell(provider.requiresCell());
+                    mCachedAttributes.setHasMonetaryCost(provider.hasMonetaryCost());
+                    mCachedAttributes.setSupportsAltitude(provider.supportsAltitude());
+                    mCachedAttributes.setSupportsSpeed(provider.supportsSpeed());
+                    mCachedAttributes.setSupportsBearing(provider.supportsBearing());
+                    mCachedAttributes.setPowerRequirement(provider.getPowerRequirement());
+                    mCachedAttributes.setAccuracy(provider.getAccuracy());
+                } catch (RemoteException e) {
+                    mCachedAttributes = null;
+                }
+            }
+
+            // resend previous values from the location manager if the service has restarted
+            try {
+                if (mEnabled) {
+                    provider.enable();
+                }
+                if (mLocationTracking) {
+                    provider.enableLocationTracking(true);
+                }
+                if (mMinTime >= 0) {
+                    provider.setMinTime(mMinTime);
+                }
+                if (mNetworkInfo != null) {
+                    provider.updateNetworkState(mNetworkState, mNetworkInfo);
+                }
+            } catch (RemoteException e) {
+            }
+        }
+    };
+
+    public String getName() {
+        return mName;
+    }
+
+    public boolean requiresNetwork() {
+        if (mCachedAttributes != null) {
+            return mCachedAttributes.requiresNetwork();
+        } else {
+            return false;
+        }
+    }
+
+    public boolean requiresSatellite() {
+        if (mCachedAttributes != null) {
+            return mCachedAttributes.requiresSatellite();
+        } else {
+            return false;
+        }
+    }
+
+    public boolean requiresCell() {
+        if (mCachedAttributes != null) {
+            return mCachedAttributes.requiresCell();
+        } else {
+            return false;
+        }
+    }
+
+    public boolean hasMonetaryCost() {
+        if (mCachedAttributes != null) {
+            return mCachedAttributes.hasMonetaryCost();
+        } else {
+            return false;
+        }
+    }
+
+    public boolean supportsAltitude() {
+        if (mCachedAttributes != null) {
+            return mCachedAttributes.supportsAltitude();
+        } else {
+            return false;
+        }
+    }
+
+    public boolean supportsSpeed() {
+        if (mCachedAttributes != null) {
+            return mCachedAttributes.supportsSpeed();
+        } else {
+            return false;
+        }
+    }
+
+     public boolean supportsBearing() {
+        if (mCachedAttributes != null) {
+            return mCachedAttributes.supportsBearing();
+        } else {
+            return false;
+        }
+    }
+
+    public int getPowerRequirement() {
+        if (mCachedAttributes != null) {
+            return mCachedAttributes.getPowerRequirement();
+        } else {
+            return -1;
+        }
+    }
+
+    public int getAccuracy() {
+        if (mCachedAttributes != null) {
+            return mCachedAttributes.getAccuracy();
+        } else {
+            return -1;
+        }
+    }
+
+    public void enable() {
+        mEnabled = true;
+        ILocationProvider provider;
+        synchronized (mServiceConnection) {
+            provider = mProvider;
+        }
+        if (provider != null) {
+            try {
+                provider.enable();
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    public void disable() {
+        mEnabled = false;
+        ILocationProvider provider;
+        synchronized (mServiceConnection) {
+            provider = mProvider;
+        }
+        if (provider != null) {
+            try {
+                provider.disable();
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    public boolean isEnabled() {
+        return mEnabled;
+    }
+
+    public int getStatus(Bundle extras) {
+        ILocationProvider provider;
+        synchronized (mServiceConnection) {
+            provider = mProvider;
+        }
+        if (provider != null) {
+            try {
+                return provider.getStatus(extras);
+            } catch (RemoteException e) {
+            }
+        }
+        return 0;
+    }
+
+    public long getStatusUpdateTime() {
+         ILocationProvider provider;
+        synchronized (mServiceConnection) {
+            provider = mProvider;
+        }
+        if (provider != null) {
+            try {
+                return provider.getStatusUpdateTime();
+            } catch (RemoteException e) {
+            }
+        }
+        return 0;
+     }
+
+    public String getInternalState() {
+        try {
+            return mProvider.getInternalState();
+        } catch (RemoteException e) {
+            Log.e(TAG, "getInternalState failed", e);
+            return null;
+        }
+    }
+
+    public boolean isLocationTracking() {
+        return mLocationTracking;
+    }
+
+    public void enableLocationTracking(boolean enable) {
+        mLocationTracking = enable;
+        if (!enable) {
+            mMinTime = -1;
+        }
+        ILocationProvider provider;
+        synchronized (mServiceConnection) {
+            provider = mProvider;
+        }
+        if (provider != null) {
+            try {
+                provider.enableLocationTracking(enable);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    public long getMinTime() {
+        return mMinTime;
+    }
+
+    public void setMinTime(long minTime) {
+       mMinTime = minTime;
+        ILocationProvider provider;
+        synchronized (mServiceConnection) {
+            provider = mProvider;
+        }
+        if (provider != null) {
+            try {
+                provider.setMinTime(minTime);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    public void updateNetworkState(int state, NetworkInfo info) {
+        mNetworkState = state;
+        mNetworkInfo = info;
+        ILocationProvider provider;
+        synchronized (mServiceConnection) {
+            provider = mProvider;
+        }
+        if (provider != null) {
+            try {
+                provider.updateNetworkState(state, info);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    public void updateLocation(Location location) {
+        ILocationProvider provider;
+        synchronized (mServiceConnection) {
+            provider = mProvider;
+        }
+        if (provider != null) {
+            try {
+                provider.updateLocation(location);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    public boolean sendExtraCommand(String command, Bundle extras) {
+        ILocationProvider provider;
+        synchronized (mServiceConnection) {
+            provider = mProvider;
+        }
+        if (provider != null) {
+            try {
+                provider.sendExtraCommand(command, extras);
+            } catch (RemoteException e) {
+            }
+        }
+        return false;
+    }
+
+    public void addListener(int uid) {
+        ILocationProvider provider;
+        synchronized (mServiceConnection) {
+            provider = mProvider;
+        }
+        if (provider != null) {
+            try {
+                provider.addListener(uid);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    public void removeListener(int uid) {
+        ILocationProvider provider;
+        synchronized (mServiceConnection) {
+            provider = mProvider;
+        }
+        if (provider != null) {
+            try {
+                provider.removeListener(uid);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+}
diff --git a/services/java/com/android/server/location/MockProvider.java b/services/java/com/android/server/location/MockProvider.java
new file mode 100644
index 0000000..e3f33469
--- /dev/null
+++ b/services/java/com/android/server/location/MockProvider.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2009 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.server.location;
+
+import android.location.ILocationManager;
+import android.location.Location;
+import android.location.LocationProvider;
+import android.net.NetworkInfo;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.PrintWriterPrinter;
+
+import java.io.PrintWriter;
+
+/**
+ * A mock location provider used by LocationManagerService to implement test providers.
+ *
+ * {@hide}
+ */
+public class MockProvider implements LocationProviderInterface {
+    private final String mName;
+    private final ILocationManager mLocationManager;
+    private final boolean mRequiresNetwork;
+    private final boolean mRequiresSatellite;
+    private final boolean mRequiresCell;
+    private final boolean mHasMonetaryCost;
+    private final boolean mSupportsAltitude;
+    private final boolean mSupportsSpeed;
+    private final boolean mSupportsBearing;
+    private final int mPowerRequirement;
+    private final int mAccuracy;
+    private final Location mLocation;
+    private int mStatus;
+    private long mStatusUpdateTime;
+    private final Bundle mExtras = new Bundle();
+    private boolean mHasLocation;
+    private boolean mHasStatus;
+    private boolean mEnabled;
+
+    private static final String TAG = "MockProvider";
+
+    public MockProvider(String name,  ILocationManager locationManager,
+        boolean requiresNetwork, boolean requiresSatellite,
+        boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude,
+        boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy) {
+        mName = name;
+        mLocationManager = locationManager;
+        mRequiresNetwork = requiresNetwork;
+        mRequiresSatellite = requiresSatellite;
+        mRequiresCell = requiresCell;
+        mHasMonetaryCost = hasMonetaryCost;
+        mSupportsAltitude = supportsAltitude;
+        mSupportsBearing = supportsBearing;
+        mSupportsSpeed = supportsSpeed;
+        mPowerRequirement = powerRequirement;
+        mAccuracy = accuracy;
+        mLocation = new Location(name);
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public void disable() {
+        mEnabled = false;
+    }
+
+    public void enable() {
+        mEnabled = true;
+    }
+
+    public boolean isEnabled() {
+        return mEnabled;
+    }
+
+    public int getStatus(Bundle extras) {
+        if (mHasStatus) {
+            extras.clear();
+            extras.putAll(mExtras);
+            return mStatus;
+        } else {
+            return LocationProvider.AVAILABLE;
+        }
+    }
+
+    public long getStatusUpdateTime() {
+        return mStatusUpdateTime;
+    }
+
+    public int getAccuracy() {
+        return mAccuracy;
+    }
+
+    public int getPowerRequirement() {
+        return mPowerRequirement;
+    }
+
+    public boolean hasMonetaryCost() {
+        return mHasMonetaryCost;
+    }
+
+    public boolean requiresCell() {
+        return mRequiresCell;
+    }
+
+    public boolean requiresNetwork() {
+        return mRequiresNetwork;
+    }
+
+    public boolean requiresSatellite() {
+        return mRequiresSatellite;
+    }
+
+    public boolean supportsAltitude() {
+        return mSupportsAltitude;
+    }
+
+    public boolean supportsBearing() {
+        return mSupportsBearing;
+    }
+
+    public boolean supportsSpeed() {
+        return mSupportsSpeed;
+    }
+
+    public void setLocation(Location l) {
+        mLocation.set(l);
+        mHasLocation = true;
+        try {
+            mLocationManager.reportLocation(mLocation, false);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException calling reportLocation");
+        }
+    }
+
+    public void clearLocation() {
+        mHasLocation = false;
+    }
+
+    public void setStatus(int status, Bundle extras, long updateTime) {
+        mStatus = status;
+        mStatusUpdateTime = updateTime;
+        mExtras.clear();
+        if (extras != null) {
+            mExtras.putAll(extras);
+        }
+        mHasStatus = true;
+    }
+
+    public void clearStatus() {
+        mHasStatus = false;
+        mStatusUpdateTime = 0;
+    }
+
+    public String getInternalState() {
+        return null;
+    }
+
+    public void enableLocationTracking(boolean enable) {
+    }
+
+    public void setMinTime(long minTime) {
+    }
+
+    public void updateNetworkState(int state, NetworkInfo info) {
+    }
+
+    public void updateLocation(Location location) {
+    }
+
+    public boolean sendExtraCommand(String command, Bundle extras) {
+        return false;
+    }
+
+    public void addListener(int uid) {
+    }
+
+    public void removeListener(int uid) {
+    }
+
+    public void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + mName);
+        pw.println(prefix + "mHasLocation=" + mHasLocation);
+        pw.println(prefix + "mLocation:");
+        mLocation.dump(new PrintWriterPrinter(pw), prefix + "  ");
+        pw.println(prefix + "mHasStatus=" + mHasStatus);
+        pw.println(prefix + "mStatus=" + mStatus);
+        pw.println(prefix + "mStatusUpdateTime=" + mStatusUpdateTime);
+        pw.println(prefix + "mExtras=" + mExtras);
+    }
+}
diff --git a/services/java/com/android/server/location/PassiveProvider.java b/services/java/com/android/server/location/PassiveProvider.java
new file mode 100644
index 0000000..5ed1558
--- /dev/null
+++ b/services/java/com/android/server/location/PassiveProvider.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2010 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.server.location;
+
+import android.location.ILocationManager;
+import android.location.Location;
+import android.location.LocationManager;
+import android.location.LocationProvider;
+import android.net.NetworkInfo;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A passive location provider reports locations received from other providers
+ * for clients that want to listen passively without actually triggering
+ * location updates.
+ *
+ * {@hide}
+ */
+public class PassiveProvider implements LocationProviderInterface {
+
+    private static final String TAG = "PassiveProvider";
+
+    private final ILocationManager mLocationManager;
+    private boolean mTracking;
+
+    public PassiveProvider(ILocationManager locationManager) {
+        mLocationManager = locationManager;
+    }
+
+    public String getName() {
+        return LocationManager.PASSIVE_PROVIDER;
+    }
+
+    public boolean requiresNetwork() {
+        return false;
+    }
+
+    public boolean requiresSatellite() {
+        return false;
+    }
+
+    public boolean requiresCell() {
+        return false;
+    }
+
+    public boolean hasMonetaryCost() {
+        return false;
+    }
+
+    public boolean supportsAltitude() {
+        return false;
+    }
+
+    public boolean supportsSpeed() {
+        return false;
+    }
+
+    public boolean supportsBearing() {
+        return false;
+    }
+
+    public int getPowerRequirement() {
+        return -1;
+    }
+
+    public int getAccuracy() {
+        return -1;
+    }
+
+    public boolean isEnabled() {
+        return true;
+    }
+
+    public void enable() {
+    }
+
+    public void disable() {
+    }
+
+    public int getStatus(Bundle extras) {
+        if (mTracking) {
+            return LocationProvider.AVAILABLE;
+        } else {
+            return LocationProvider.TEMPORARILY_UNAVAILABLE;
+        }
+    }
+
+    public long getStatusUpdateTime() {
+        return -1;
+    }
+
+    public String getInternalState() {
+        return null;
+    }
+
+    public void enableLocationTracking(boolean enable) {
+        mTracking = enable;
+    }
+
+    public void setMinTime(long minTime) {
+    }
+
+    public void updateNetworkState(int state, NetworkInfo info) {
+    }
+
+    public void updateLocation(Location location) {
+        if (mTracking) {
+            try {
+                // pass the location back to the location manager
+                mLocationManager.reportLocation(location, true);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException calling reportLocation");
+            }
+        }
+    }
+
+    public boolean sendExtraCommand(String command, Bundle extras) {
+        return false;
+    }
+
+    public void addListener(int uid) {
+    }
+
+    public void removeListener(int uid) {
+    }
+}
diff --git a/services/java/com/android/server/status/StatusBarPolicy.java b/services/java/com/android/server/status/StatusBarPolicy.java
index 55840e2..efb1a06 100644
--- a/services/java/com/android/server/status/StatusBarPolicy.java
+++ b/services/java/com/android/server/status/StatusBarPolicy.java
@@ -30,6 +30,7 @@
 import android.content.res.TypedArray;
 import android.graphics.PixelFormat;
 import android.graphics.drawable.Drawable;
+import android.location.LocationManager;
 import android.media.AudioManager;
 import android.media.Ringtone;
 import android.media.RingtoneManager;
@@ -62,7 +63,6 @@
 
 import com.android.internal.R;
 import com.android.internal.app.IBatteryStats;
-import com.android.internal.location.GpsLocationProvider;
 import com.android.internal.telephony.IccCard;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.cdma.EriInfo;
@@ -387,8 +387,8 @@
                     action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
                 updateWifi(intent);
             }
-            else if (action.equals(GpsLocationProvider.GPS_ENABLED_CHANGE_ACTION) ||
-                    action.equals(GpsLocationProvider.GPS_FIX_CHANGE_ACTION)) {
+            else if (action.equals(LocationManager.GPS_ENABLED_CHANGE_ACTION) ||
+                    action.equals(LocationManager.GPS_FIX_CHANGE_ACTION)) {
                 updateGps(intent);
             }
             else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION) ||
@@ -533,8 +533,8 @@
         filter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
         filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
         filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
-        filter.addAction(GpsLocationProvider.GPS_ENABLED_CHANGE_ACTION);
-        filter.addAction(GpsLocationProvider.GPS_FIX_CHANGE_ACTION);
+        filter.addAction(LocationManager.GPS_ENABLED_CHANGE_ACTION);
+        filter.addAction(LocationManager.GPS_FIX_CHANGE_ACTION);
         filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
         filter.addAction(TtyIntent.TTY_ENABLED_CHANGE_ACTION);
         mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
@@ -1286,13 +1286,13 @@
 
     private final void updateGps(Intent intent) {
         final String action = intent.getAction();
-        final boolean enabled = intent.getBooleanExtra(GpsLocationProvider.EXTRA_ENABLED, false);
+        final boolean enabled = intent.getBooleanExtra(LocationManager.EXTRA_GPS_ENABLED, false);
 
-        if (action.equals(GpsLocationProvider.GPS_FIX_CHANGE_ACTION) && enabled) {
+        if (action.equals(LocationManager.GPS_FIX_CHANGE_ACTION) && enabled) {
             // GPS is getting fixes
             mService.updateIcon(mGpsIcon, mGpsFixIconData, null);
             mService.setIconVisibility(mGpsIcon, true);
-        } else if (action.equals(GpsLocationProvider.GPS_ENABLED_CHANGE_ACTION) && !enabled) {
+        } else if (action.equals(LocationManager.GPS_ENABLED_CHANGE_ACTION) && !enabled) {
             // GPS is off
             mService.setIconVisibility(mGpsIcon, false);
         } else {
diff --git a/services/jni/Android.mk b/services/jni/Android.mk
index 9d2760e..b90e327 100644
--- a/services/jni/Android.mk
+++ b/services/jni/Android.mk
@@ -9,6 +9,7 @@
     com_android_server_SensorService.cpp \
     com_android_server_SystemServer.cpp \
     com_android_server_VibratorService.cpp \
+	com_android_server_location_GpsLocationProvider.cpp \
     onload.cpp
 
 LOCAL_C_INCLUDES += \
diff --git a/services/jni/com_android_server_location_GpsLocationProvider.cpp b/services/jni/com_android_server_location_GpsLocationProvider.cpp
new file mode 100755
index 0000000..003f109
--- /dev/null
+++ b/services/jni/com_android_server_location_GpsLocationProvider.cpp
@@ -0,0 +1,531 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "GpsLocationProvider"
+
+//#define LOG_NDDEBUG 0
+
+#include "JNIHelp.h"
+#include "jni.h"
+#include "hardware_legacy/gps.h"
+#include "hardware_legacy/gps_ni.h"
+#include "utils/Log.h"
+#include "utils/misc.h"
+
+#include <string.h>
+#include <pthread.h>
+
+static pthread_mutex_t sEventMutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t sEventCond = PTHREAD_COND_INITIALIZER;
+static jmethodID method_reportLocation;
+static jmethodID method_reportStatus;
+static jmethodID method_reportSvStatus;
+static jmethodID method_reportAGpsStatus;
+static jmethodID method_reportNmea;
+static jmethodID method_xtraDownloadRequest;
+static jmethodID method_reportNiNotification;
+
+static const GpsInterface* sGpsInterface = NULL;
+static const GpsXtraInterface* sGpsXtraInterface = NULL;
+static const AGpsInterface* sAGpsInterface = NULL;
+static const GpsNiInterface* sGpsNiInterface = NULL;
+static const GpsDebugInterface* sGpsDebugInterface = NULL;
+
+// data written to by GPS callbacks
+static GpsLocation  sGpsLocation;
+static GpsStatus    sGpsStatus;
+static GpsSvStatus  sGpsSvStatus;
+static AGpsStatus   sAGpsStatus;
+static GpsNiNotification  sGpsNiNotification;
+
+// buffer for NMEA data
+#define NMEA_SENTENCE_LENGTH    100
+#define NMEA_SENTENCE_COUNT     40
+struct NmeaSentence {
+    GpsUtcTime  timestamp;
+    char        nmea[NMEA_SENTENCE_LENGTH];
+};
+static NmeaSentence sNmeaBuffer[NMEA_SENTENCE_COUNT];
+static int mNmeaSentenceCount = 0;
+
+// a copy of the data shared by android_location_GpsLocationProvider_wait_for_event
+// and android_location_GpsLocationProvider_read_status
+static GpsLocation  sGpsLocationCopy;
+static GpsStatus    sGpsStatusCopy;
+static GpsSvStatus  sGpsSvStatusCopy;
+static AGpsStatus   sAGpsStatusCopy;
+static NmeaSentence sNmeaBufferCopy[NMEA_SENTENCE_COUNT];
+static GpsNiNotification  sGpsNiNotificationCopy;
+
+enum CallbackType {
+    kLocation = 1,
+    kStatus = 2,
+    kSvStatus = 4,
+    kAGpsStatus = 8,
+    kXtraDownloadRequest = 16,
+    kDisableRequest = 32,
+    kNmeaAvailable = 64,
+    kNiNotification = 128,
+}; 
+static int sPendingCallbacks;
+
+namespace android {
+
+static void location_callback(GpsLocation* location)
+{
+    pthread_mutex_lock(&sEventMutex);
+
+    sPendingCallbacks |= kLocation;
+    memcpy(&sGpsLocation, location, sizeof(sGpsLocation));
+
+    pthread_cond_signal(&sEventCond);
+    pthread_mutex_unlock(&sEventMutex);
+}
+
+static void status_callback(GpsStatus* status)
+{
+    pthread_mutex_lock(&sEventMutex);
+
+    sPendingCallbacks |= kStatus;
+    memcpy(&sGpsStatus, status, sizeof(sGpsStatus));
+
+    pthread_cond_signal(&sEventCond);
+    pthread_mutex_unlock(&sEventMutex);
+}
+
+static void sv_status_callback(GpsSvStatus* sv_status)
+{
+    pthread_mutex_lock(&sEventMutex);
+
+    sPendingCallbacks |= kSvStatus;
+    memcpy(&sGpsSvStatus, sv_status, sizeof(GpsSvStatus));
+
+    pthread_cond_signal(&sEventCond);
+    pthread_mutex_unlock(&sEventMutex);
+}
+
+static void nmea_callback(GpsUtcTime timestamp, const char* nmea, int length)
+{
+    pthread_mutex_lock(&sEventMutex);
+
+    if (length >= NMEA_SENTENCE_LENGTH) {
+        LOGE("NMEA data too long in nmea_callback (length = %d)\n", length);
+        length = NMEA_SENTENCE_LENGTH - 1;
+    }
+    if (mNmeaSentenceCount >= NMEA_SENTENCE_COUNT) {
+        LOGE("NMEA data overflowed buffer\n");
+        pthread_mutex_unlock(&sEventMutex);
+        return;
+    }
+
+    sPendingCallbacks |= kNmeaAvailable;
+    sNmeaBuffer[mNmeaSentenceCount].timestamp = timestamp;
+    memcpy(sNmeaBuffer[mNmeaSentenceCount].nmea, nmea, length);
+    sNmeaBuffer[mNmeaSentenceCount].nmea[length] = 0;
+    mNmeaSentenceCount++;
+
+    pthread_cond_signal(&sEventCond);
+    pthread_mutex_unlock(&sEventMutex);
+}
+
+static void agps_status_callback(AGpsStatus* agps_status)
+{
+    pthread_mutex_lock(&sEventMutex);
+
+    sPendingCallbacks |= kAGpsStatus;
+    memcpy(&sAGpsStatus, agps_status, sizeof(AGpsStatus));
+
+    pthread_cond_signal(&sEventCond);
+    pthread_mutex_unlock(&sEventMutex);
+}
+
+GpsCallbacks sGpsCallbacks = {
+    location_callback,
+    status_callback,
+    sv_status_callback,
+    nmea_callback
+};
+
+static void
+download_request_callback()
+{
+    pthread_mutex_lock(&sEventMutex);
+    sPendingCallbacks |= kXtraDownloadRequest;
+    pthread_cond_signal(&sEventCond);
+    pthread_mutex_unlock(&sEventMutex);
+}
+
+static void
+gps_ni_notify_callback(GpsNiNotification *notification)
+{
+   LOGD("gps_ni_notify_callback: notif=%d", notification->notification_id);
+
+   pthread_mutex_lock(&sEventMutex);
+
+   sPendingCallbacks |= kNiNotification;
+   memcpy(&sGpsNiNotification, notification, sizeof(GpsNiNotification));
+
+   pthread_cond_signal(&sEventCond);
+   pthread_mutex_unlock(&sEventMutex);
+}
+
+GpsXtraCallbacks sGpsXtraCallbacks = {
+    download_request_callback,
+};
+
+AGpsCallbacks sAGpsCallbacks = {
+    agps_status_callback,
+};
+
+GpsNiCallbacks sGpsNiCallbacks = {
+    gps_ni_notify_callback,
+};
+
+static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env, jclass clazz) {
+    method_reportLocation = env->GetMethodID(clazz, "reportLocation", "(IDDDFFFJ)V");
+    method_reportStatus = env->GetMethodID(clazz, "reportStatus", "(I)V");
+    method_reportSvStatus = env->GetMethodID(clazz, "reportSvStatus", "()V");
+    method_reportAGpsStatus = env->GetMethodID(clazz, "reportAGpsStatus", "(II)V");
+    method_reportNmea = env->GetMethodID(clazz, "reportNmea", "(IJ)V");
+    method_xtraDownloadRequest = env->GetMethodID(clazz, "xtraDownloadRequest", "()V");
+    method_reportNiNotification = env->GetMethodID(clazz, "reportNiNotification", "(IIIIILjava/lang/String;Ljava/lang/String;IILjava/lang/String;)V");
+}
+
+static jboolean android_location_GpsLocationProvider_is_supported(JNIEnv* env, jclass clazz) {
+    if (!sGpsInterface)
+        sGpsInterface = gps_get_interface();
+    return (sGpsInterface != NULL);
+}
+
+static jboolean android_location_GpsLocationProvider_init(JNIEnv* env, jobject obj)
+{
+    if (!sGpsInterface)
+        sGpsInterface = gps_get_interface();
+    if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0)
+        return false;
+
+    if (!sAGpsInterface)
+        sAGpsInterface = (const AGpsInterface*)sGpsInterface->get_extension(AGPS_INTERFACE);
+    if (sAGpsInterface)
+        sAGpsInterface->init(&sAGpsCallbacks);
+
+    if (!sGpsNiInterface)
+       sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
+    if (sGpsNiInterface)
+       sGpsNiInterface->init(&sGpsNiCallbacks);
+
+    if (!sGpsDebugInterface)
+       sGpsDebugInterface = (const GpsDebugInterface*)sGpsInterface->get_extension(GPS_DEBUG_INTERFACE);
+
+    return true;
+}
+
+static void android_location_GpsLocationProvider_disable(JNIEnv* env, jobject obj)
+{
+    pthread_mutex_lock(&sEventMutex);
+    sPendingCallbacks |= kDisableRequest;
+    pthread_cond_signal(&sEventCond);
+    pthread_mutex_unlock(&sEventMutex);
+}
+
+static void android_location_GpsLocationProvider_cleanup(JNIEnv* env, jobject obj)
+{
+    sGpsInterface->cleanup();
+}
+
+static jboolean android_location_GpsLocationProvider_start(JNIEnv* env, jobject obj, jint positionMode,
+        jboolean singleFix, jint fixFrequency)
+{
+    int result = sGpsInterface->set_position_mode(positionMode, (singleFix ? 0 : fixFrequency));
+    if (result) {
+        return false;
+    }
+
+    return (sGpsInterface->start() == 0);
+}
+
+static jboolean android_location_GpsLocationProvider_stop(JNIEnv* env, jobject obj)
+{
+    return (sGpsInterface->stop() == 0);
+}
+
+static void android_location_GpsLocationProvider_delete_aiding_data(JNIEnv* env, jobject obj, jint flags)
+{
+    sGpsInterface->delete_aiding_data(flags);
+}
+
+static void android_location_GpsLocationProvider_wait_for_event(JNIEnv* env, jobject obj)
+{
+    pthread_mutex_lock(&sEventMutex);
+    while (sPendingCallbacks == 0) {
+        pthread_cond_wait(&sEventCond, &sEventMutex);
+    }
+
+    // copy and clear the callback flags
+    int pendingCallbacks = sPendingCallbacks;
+    sPendingCallbacks = 0;
+    int nmeaSentenceCount = mNmeaSentenceCount;
+    mNmeaSentenceCount = 0;
+    
+    // copy everything and unlock the mutex before calling into Java code to avoid the possibility
+    // of timeouts in the GPS engine.
+    if (pendingCallbacks & kLocation)
+        memcpy(&sGpsLocationCopy, &sGpsLocation, sizeof(sGpsLocationCopy));
+    if (pendingCallbacks & kStatus)
+        memcpy(&sGpsStatusCopy, &sGpsStatus, sizeof(sGpsStatusCopy));
+    if (pendingCallbacks & kSvStatus)
+        memcpy(&sGpsSvStatusCopy, &sGpsSvStatus, sizeof(sGpsSvStatusCopy));
+    if (pendingCallbacks & kAGpsStatus)
+        memcpy(&sAGpsStatusCopy, &sAGpsStatus, sizeof(sAGpsStatusCopy));
+    if (pendingCallbacks & kNmeaAvailable)
+        memcpy(&sNmeaBufferCopy, &sNmeaBuffer, nmeaSentenceCount * sizeof(sNmeaBuffer[0]));
+    if (pendingCallbacks & kNiNotification)
+        memcpy(&sGpsNiNotificationCopy, &sGpsNiNotification, sizeof(sGpsNiNotificationCopy));
+    pthread_mutex_unlock(&sEventMutex);   
+
+    if (pendingCallbacks & kLocation) {
+        env->CallVoidMethod(obj, method_reportLocation, sGpsLocationCopy.flags,
+                (jdouble)sGpsLocationCopy.latitude, (jdouble)sGpsLocationCopy.longitude,
+                (jdouble)sGpsLocationCopy.altitude,
+                (jfloat)sGpsLocationCopy.speed, (jfloat)sGpsLocationCopy.bearing,
+                (jfloat)sGpsLocationCopy.accuracy, (jlong)sGpsLocationCopy.timestamp);
+    }
+    if (pendingCallbacks & kStatus) {
+        env->CallVoidMethod(obj, method_reportStatus, sGpsStatusCopy.status);
+    }
+    if (pendingCallbacks & kSvStatus) {
+        env->CallVoidMethod(obj, method_reportSvStatus);
+    }
+    if (pendingCallbacks & kAGpsStatus) {
+        env->CallVoidMethod(obj, method_reportAGpsStatus, sAGpsStatusCopy.type, sAGpsStatusCopy.status);
+    }  
+    if (pendingCallbacks & kNmeaAvailable) {
+        for (int i = 0; i < nmeaSentenceCount; i++) {
+            env->CallVoidMethod(obj, method_reportNmea, i, sNmeaBuffer[i].timestamp);
+        }
+    }
+    if (pendingCallbacks & kXtraDownloadRequest) {
+        env->CallVoidMethod(obj, method_xtraDownloadRequest);
+    }
+    if (pendingCallbacks & kDisableRequest) {
+        // don't need to do anything - we are just poking so wait_for_event will return.
+    }
+    if (pendingCallbacks & kNiNotification) {
+       LOGD("android_location_GpsLocationProvider_wait_for_event: sent notification callback.");
+       jstring reqId = env->NewStringUTF(sGpsNiNotificationCopy.requestor_id);
+       jstring text = env->NewStringUTF(sGpsNiNotificationCopy.text);
+       jstring extras = env->NewStringUTF(sGpsNiNotificationCopy.extras);
+       env->CallVoidMethod(obj, method_reportNiNotification,
+             sGpsNiNotificationCopy.notification_id,
+             sGpsNiNotificationCopy.ni_type,
+             sGpsNiNotificationCopy.notify_flags,
+             sGpsNiNotificationCopy.timeout,
+             sGpsNiNotificationCopy.default_response,
+             reqId,
+             text,
+             sGpsNiNotificationCopy.requestor_id_encoding,
+             sGpsNiNotificationCopy.text_encoding,
+             extras
+       );
+    }
+}
+
+static jint android_location_GpsLocationProvider_read_sv_status(JNIEnv* env, jobject obj,
+        jintArray prnArray, jfloatArray snrArray, jfloatArray elevArray, jfloatArray azumArray,
+        jintArray maskArray)
+{
+    // this should only be called from within a call to reportStatus, so we don't need to lock here
+
+    jint* prns = env->GetIntArrayElements(prnArray, 0);
+    jfloat* snrs = env->GetFloatArrayElements(snrArray, 0);
+    jfloat* elev = env->GetFloatArrayElements(elevArray, 0);
+    jfloat* azim = env->GetFloatArrayElements(azumArray, 0);
+    jint* mask = env->GetIntArrayElements(maskArray, 0);
+
+    int num_svs = sGpsSvStatusCopy.num_svs;
+    for (int i = 0; i < num_svs; i++) {
+        prns[i] = sGpsSvStatusCopy.sv_list[i].prn;
+        snrs[i] = sGpsSvStatusCopy.sv_list[i].snr;
+        elev[i] = sGpsSvStatusCopy.sv_list[i].elevation;
+        azim[i] = sGpsSvStatusCopy.sv_list[i].azimuth;
+    }
+    mask[0] = sGpsSvStatusCopy.ephemeris_mask;
+    mask[1] = sGpsSvStatusCopy.almanac_mask;
+    mask[2] = sGpsSvStatusCopy.used_in_fix_mask;
+
+    env->ReleaseIntArrayElements(prnArray, prns, 0);
+    env->ReleaseFloatArrayElements(snrArray, snrs, 0);
+    env->ReleaseFloatArrayElements(elevArray, elev, 0);
+    env->ReleaseFloatArrayElements(azumArray, azim, 0);
+    env->ReleaseIntArrayElements(maskArray, mask, 0);
+    return num_svs;
+}
+
+static jint android_location_GpsLocationProvider_read_nmea(JNIEnv* env, jobject obj, jint index, jbyteArray nmeaArray, jint buffer_size)
+{
+    // this should only be called from within a call to reportNmea, so we don't need to lock here
+
+    jbyte* nmea = env->GetByteArrayElements(nmeaArray, 0);
+
+    int length = strlen(sNmeaBufferCopy[index].nmea);
+    if (length > buffer_size)
+        length = buffer_size;
+    memcpy(nmea, sNmeaBufferCopy[index].nmea, length);
+
+    env->ReleaseByteArrayElements(nmeaArray, nmea, 0);
+    return length;
+}
+
+static void android_location_GpsLocationProvider_inject_time(JNIEnv* env, jobject obj, jlong time, 
+        jlong timeReference, jint uncertainty)
+{
+    sGpsInterface->inject_time(time, timeReference, uncertainty);
+}
+
+static void android_location_GpsLocationProvider_inject_location(JNIEnv* env, jobject obj,
+        jdouble latitude, jdouble longitude, jfloat accuracy)
+{
+    sGpsInterface->inject_location(latitude, longitude, accuracy);
+}
+
+static jboolean android_location_GpsLocationProvider_supports_xtra(JNIEnv* env, jobject obj)
+{
+    if (!sGpsXtraInterface) {
+        sGpsXtraInterface = (const GpsXtraInterface*)sGpsInterface->get_extension(GPS_XTRA_INTERFACE);
+        if (sGpsXtraInterface) {
+            int result = sGpsXtraInterface->init(&sGpsXtraCallbacks);
+            if (result) {
+                sGpsXtraInterface = NULL;
+            }
+        }
+    }
+
+    return (sGpsXtraInterface != NULL);
+}
+
+static void android_location_GpsLocationProvider_inject_xtra_data(JNIEnv* env, jobject obj,
+        jbyteArray data, jint length)
+{
+    jbyte* bytes = env->GetByteArrayElements(data, 0);
+    sGpsXtraInterface->inject_xtra_data((char *)bytes, length);
+    env->ReleaseByteArrayElements(data, bytes, 0);
+}
+
+static void android_location_GpsLocationProvider_agps_data_conn_open(JNIEnv* env, jobject obj, jstring apn)
+{
+    if (!sAGpsInterface) {
+        sAGpsInterface = (const AGpsInterface*)sGpsInterface->get_extension(AGPS_INTERFACE);
+    }
+    if (sAGpsInterface) {
+        if (apn == NULL) {
+            jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
+            return;
+        }
+        const char *apnStr = env->GetStringUTFChars(apn, NULL);
+        sAGpsInterface->data_conn_open(apnStr);
+        env->ReleaseStringUTFChars(apn, apnStr);
+    }
+}
+
+static void android_location_GpsLocationProvider_agps_data_conn_closed(JNIEnv* env, jobject obj)
+{
+    if (!sAGpsInterface) {
+        sAGpsInterface = (const AGpsInterface*)sGpsInterface->get_extension(AGPS_INTERFACE);
+    }
+    if (sAGpsInterface) {
+        sAGpsInterface->data_conn_closed();
+    }
+}
+
+static void android_location_GpsLocationProvider_agps_data_conn_failed(JNIEnv* env, jobject obj)
+{
+    if (!sAGpsInterface) {
+        sAGpsInterface = (const AGpsInterface*)sGpsInterface->get_extension(AGPS_INTERFACE);
+    }
+    if (sAGpsInterface) {
+        sAGpsInterface->data_conn_failed();
+    }
+}
+
+static void android_location_GpsLocationProvider_set_agps_server(JNIEnv* env, jobject obj,
+        jint type, jstring hostname, jint port)
+{
+    if (!sAGpsInterface) {
+        sAGpsInterface = (const AGpsInterface*)sGpsInterface->get_extension(AGPS_INTERFACE);
+    }
+    if (sAGpsInterface) {
+        const char *c_hostname = env->GetStringUTFChars(hostname, NULL);
+        sAGpsInterface->set_server(type, c_hostname, port);
+        env->ReleaseStringUTFChars(hostname, c_hostname);
+    }
+}
+
+static void android_location_GpsLocationProvider_send_ni_response(JNIEnv* env, jobject obj,
+      jint notifId, jint response)
+{
+    if (!sGpsNiInterface) {
+        sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE);
+    }
+    if (sGpsNiInterface) {
+        sGpsNiInterface->respond(notifId, response);
+    }
+}
+
+static jstring android_location_GpsLocationProvider_get_internal_state(JNIEnv* env, jobject obj)
+{
+    jstring result = NULL;
+    if (sGpsDebugInterface) {
+        const size_t maxLength = 2047;
+        char buffer[maxLength+1];
+        size_t length = sGpsDebugInterface->get_internal_state(buffer, maxLength);
+        if (length > maxLength) length = maxLength;
+        buffer[length] = 0;
+        result = env->NewStringUTF(buffer);
+    }
+    return result;
+}
+
+static JNINativeMethod sMethods[] = {
+     /* name, signature, funcPtr */
+    {"class_init_native", "()V", (void *)android_location_GpsLocationProvider_class_init_native},
+    {"native_is_supported", "()Z", (void*)android_location_GpsLocationProvider_is_supported},
+    {"native_init", "()Z", (void*)android_location_GpsLocationProvider_init},
+    {"native_disable", "()V", (void*)android_location_GpsLocationProvider_disable},
+    {"native_cleanup", "()V", (void*)android_location_GpsLocationProvider_cleanup},
+    {"native_start", "(IZI)Z", (void*)android_location_GpsLocationProvider_start},
+    {"native_stop", "()Z", (void*)android_location_GpsLocationProvider_stop},
+    {"native_delete_aiding_data", "(I)V", (void*)android_location_GpsLocationProvider_delete_aiding_data},
+    {"native_wait_for_event", "()V", (void*)android_location_GpsLocationProvider_wait_for_event},
+    {"native_read_sv_status", "([I[F[F[F[I)I", (void*)android_location_GpsLocationProvider_read_sv_status},
+    {"native_read_nmea", "(I[BI)I", (void*)android_location_GpsLocationProvider_read_nmea},
+    {"native_inject_time", "(JJI)V", (void*)android_location_GpsLocationProvider_inject_time},
+    {"native_inject_location", "(DDF)V", (void*)android_location_GpsLocationProvider_inject_location},
+    {"native_supports_xtra", "()Z", (void*)android_location_GpsLocationProvider_supports_xtra},
+    {"native_inject_xtra_data", "([BI)V", (void*)android_location_GpsLocationProvider_inject_xtra_data},
+    {"native_agps_data_conn_open", "(Ljava/lang/String;)V", (void*)android_location_GpsLocationProvider_agps_data_conn_open},
+    {"native_agps_data_conn_closed", "()V", (void*)android_location_GpsLocationProvider_agps_data_conn_closed},
+    {"native_agps_data_conn_failed", "()V", (void*)android_location_GpsLocationProvider_agps_data_conn_failed},
+    {"native_set_agps_server", "(ILjava/lang/String;I)V", (void*)android_location_GpsLocationProvider_set_agps_server},
+    {"native_send_ni_response", "(II)V", (void*)android_location_GpsLocationProvider_send_ni_response},
+    {"native_get_internal_state", "()Ljava/lang/String;", (void*)android_location_GpsLocationProvider_get_internal_state},
+};
+
+int register_android_server_location_GpsLocationProvider(JNIEnv* env)
+{
+    return jniRegisterNativeMethods(env, "com/android/server/location/GpsLocationProvider", sMethods, NELEM(sMethods));
+}
+
+} /* namespace android */
diff --git a/services/jni/onload.cpp b/services/jni/onload.cpp
index c16fdb8..d11e7e1 100644
--- a/services/jni/onload.cpp
+++ b/services/jni/onload.cpp
@@ -11,6 +11,7 @@
 int register_android_server_SensorService(JNIEnv* env);
 int register_android_server_VibratorService(JNIEnv* env);
 int register_android_server_SystemServer(JNIEnv* env);
+int register_android_server_location_GpsLocationProvider(JNIEnv* env);
 };
 
 using namespace android;
@@ -33,6 +34,7 @@
     register_android_server_SensorService(env);
     register_android_server_VibratorService(env);
     register_android_server_SystemServer(env);
+    register_android_server_location_GpsLocationProvider(env);
 
     return JNI_VERSION_1_4;
 }