gps: Network initiated SUPL

Initial contribution from Qualcomm.

Signed-off-by: Mike Lockwood <lockwood@android.com>
diff --git a/Android.mk b/Android.mk
index 3653c7b..d348a52 100644
--- a/Android.mk
+++ b/Android.mk
@@ -147,6 +147,7 @@
 	location/java/android/location/ILocationListener.aidl \
 	location/java/android/location/ILocationManager.aidl \
 	location/java/android/location/ILocationProvider.aidl \
+	location/java/android/location/INetInitiatedListener.aidl \
 	media/java/android/media/IAudioService.aidl \
 	media/java/android/media/IMediaScannerListener.aidl \
 	media/java/android/media/IMediaScannerService.aidl \
diff --git a/core/java/com/android/internal/app/NetInitiatedActivity.java b/core/java/com/android/internal/app/NetInitiatedActivity.java
new file mode 100755
index 0000000..98fb236
--- /dev/null
+++ b/core/java/com/android/internal/app/NetInitiatedActivity.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2007 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IMountService;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.widget.Toast;
+import android.util.Log;
+import android.location.LocationManager;
+import com.android.internal.location.GpsLocationProvider;
+import com.android.internal.location.GpsNetInitiatedHandler;
+
+/**
+ * This activity is shown to the user for him/her to accept or deny network-initiated
+ * requests. It uses the alert dialog style. It will be launched from a notification.
+ */
+public class NetInitiatedActivity extends AlertActivity implements DialogInterface.OnClickListener {
+
+    private static final String TAG = "NetInitiatedActivity";
+
+    private static final boolean DEBUG = true;
+    private static final boolean VERBOSE = false;
+
+    private static final int POSITIVE_BUTTON = AlertDialog.BUTTON1;
+    private static final int NEGATIVE_BUTTON = AlertDialog.BUTTON2;
+
+    // Dialog button text
+    public static final String BUTTON_TEXT_ACCEPT = "Accept";
+    public static final String BUTTON_TEXT_DENY = "Deny";
+
+    // Received ID from intent, -1 when no notification is in progress
+    private int notificationId = -1;
+
+    /** Used to detect when NI request is received */
+    private BroadcastReceiver mNetInitiatedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (DEBUG) Log.d(TAG, "NetInitiatedReceiver onReceive: " + intent.getAction());
+            if (intent.getAction() == GpsNetInitiatedHandler.ACTION_NI_VERIFY) {
+                handleNIVerify(intent);
+            }
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Set up the "dialog"
+        final Intent intent = getIntent();
+        final AlertController.AlertParams p = mAlertParams;
+        p.mIconId = com.android.internal.R.drawable.ic_dialog_usb;
+        p.mTitle = intent.getStringExtra(GpsNetInitiatedHandler.NI_INTENT_KEY_TITLE);
+        p.mMessage = intent.getStringExtra(GpsNetInitiatedHandler.NI_INTENT_KEY_MESSAGE);
+        p.mPositiveButtonText = BUTTON_TEXT_ACCEPT;
+        p.mPositiveButtonListener = this;
+        p.mNegativeButtonText = BUTTON_TEXT_DENY;
+        p.mNegativeButtonListener = this;
+
+        notificationId = intent.getIntExtra(GpsNetInitiatedHandler.NI_INTENT_KEY_NOTIF_ID, -1);
+        if (DEBUG) Log.d(TAG, "onCreate, notifId: " + notificationId);
+
+        setupAlert();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (DEBUG) Log.d(TAG, "onResume");
+        registerReceiver(mNetInitiatedReceiver, new IntentFilter(GpsNetInitiatedHandler.ACTION_NI_VERIFY));
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        if (DEBUG) Log.d(TAG, "onPause");
+        unregisterReceiver(mNetInitiatedReceiver);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void onClick(DialogInterface dialog, int which) {
+        if (which == POSITIVE_BUTTON) {
+            sendUserResponse(GpsNetInitiatedHandler.GPS_NI_RESPONSE_ACCEPT);
+        }
+        if (which == NEGATIVE_BUTTON) {
+            sendUserResponse(GpsNetInitiatedHandler.GPS_NI_RESPONSE_DENY);
+        }
+
+        // No matter what, finish the activity
+        finish();
+        notificationId = -1;
+    }
+
+    // Respond to NI Handler under GpsLocationProvider, 1 = accept, 2 = deny
+    private void sendUserResponse(int response) {
+        if (DEBUG) Log.d(TAG, "sendUserResponse, response: " + response);
+        LocationManager locationManager = (LocationManager)
+            this.getSystemService(Context.LOCATION_SERVICE);
+        locationManager.sendNiResponse(notificationId, response);
+    }
+
+    private void handleNIVerify(Intent intent) {
+        int notifId = intent.getIntExtra(GpsNetInitiatedHandler.NI_INTENT_KEY_NOTIF_ID, -1);
+        notificationId = notifId;
+
+        if (DEBUG) Log.d(TAG, "handleNIVerify action: " + intent.getAction());
+    }
+
+    private void showNIError() {
+        Toast.makeText(this, "NI error" /* com.android.internal.R.string.usb_storage_error_message */,
+                Toast.LENGTH_LONG).show();
+    }
+}
diff --git a/core/jni/android_location_GpsLocationProvider.cpp b/core/jni/android_location_GpsLocationProvider.cpp
index 90a0487..c329602 100755
--- a/core/jni/android_location_GpsLocationProvider.cpp
+++ b/core/jni/android_location_GpsLocationProvider.cpp
@@ -16,16 +16,18 @@
 
 #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;
@@ -34,16 +36,19 @@
 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;
 
 // 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
@@ -62,6 +67,7 @@
 static GpsSvStatus  sGpsSvStatusCopy;
 static AGpsStatus   sAGpsStatusCopy;
 static NmeaSentence sNmeaBufferCopy[NMEA_SENTENCE_LENGTH];
+static GpsNiNotification  sGpsNiNotificationCopy;
 
 enum CallbackType {
     kLocation = 1,
@@ -71,6 +77,7 @@
     kXtraDownloadRequest = 16,
     kDisableRequest = 32,
     kNmeaAvailable = 64,
+    kNiNotification = 128,
 }; 
 static int sPendingCallbacks;
 
@@ -160,6 +167,20 @@
     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,
 };
@@ -168,6 +189,10 @@
     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");
@@ -175,6 +200,7 @@
     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) {
@@ -194,6 +220,12 @@
         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);
+
     return true;
 }
 
@@ -210,7 +242,7 @@
     sGpsInterface->cleanup();
 }
 
-static jboolean android_location_GpsLocationProvider_start(JNIEnv* env, jobject obj, jint positionMode, 
+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));
@@ -235,7 +267,7 @@
 {
     pthread_mutex_lock(&sEventMutex);
     pthread_cond_wait(&sEventCond, &sEventMutex);
-    
+
     // copy and clear the callback flags
     int pendingCallbacks = sPendingCallbacks;
     sPendingCallbacks = 0;
@@ -254,18 +286,20 @@
         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) { 
+    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, 
+                (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);
     }
@@ -277,16 +311,34 @@
             env->CallVoidMethod(obj, method_reportNmea, i, sNmeaBuffer[i].timestamp);
         }
     }
-    if (pendingCallbacks & kXtraDownloadRequest) {    
+    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, 
+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
@@ -358,7 +410,7 @@
     return (sGpsXtraInterface != NULL);
 }
 
-static void android_location_GpsLocationProvider_inject_xtra_data(JNIEnv* env, jobject obj, 
+static void android_location_GpsLocationProvider_inject_xtra_data(JNIEnv* env, jobject obj,
         jbyteArray data, jint length)
 {
     jbyte* bytes = env->GetByteArrayElements(data, 0);
@@ -415,6 +467,16 @@
     }
 }
 
+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 JNINativeMethod sMethods[] = {
      /* name, signature, funcPtr */
     {"class_init_native", "()V", (void *)android_location_GpsLocationProvider_class_init_native},
@@ -436,6 +498,7 @@
     {"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},
 };
 
 int register_android_location_GpsLocationProvider(JNIEnv* env)
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5b9f7e6..4f789dd 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1163,6 +1163,10 @@
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
+        <activity android:name="com.android.internal.app.NetInitiatedActivity"
+                android:theme="@style/Theme.Dialog.Alert"
+                android:excludeFromRecents="true">
+        </activity>
 
         <service android:name="com.android.server.LoadAverageService"
                 android:exported="true" />
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index caf9516..b6c59d6 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -83,4 +83,7 @@
     /* for installing external Location Providers */
     void installLocationProvider(String name, ILocationProvider provider);
     void installGeocodeProvider(IGeocodeProvider provider);
+
+    // for NI support
+    boolean sendNiResponse(int notifId, int userResponse);
 }
diff --git a/location/java/android/location/INetInitiatedListener.aidl b/location/java/android/location/INetInitiatedListener.aidl
new file mode 100755
index 0000000..f2f5a32
--- /dev/null
+++ b/location/java/android/location/INetInitiatedListener.aidl
@@ -0,0 +1,26 @@
+/*

+**

+** Copyright 2008, The Android Open Source Project

+**

+** Licensed under the Apache License, Version 2.0 (the "License");

+** you may not use this file except in compliance with the License.

+** You may obtain a copy of the License at

+**

+**     http://www.apache.org/licenses/LICENSE-2.0

+**

+** Unless required by applicable law or agreed to in writing, software

+** distributed under the License is distributed on an "AS IS" BASIS,

+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+** See the License for the specific language governing permissions and

+** limitations under the License.

+*/

+

+package android.location;

+

+/**

+ * {@hide}

+ */

+interface INetInitiatedListener

+{

+    boolean sendNiResponse(int notifId, int userResponse);

+}

diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 8f0352d..8326361 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -1417,4 +1417,20 @@
             Log.e(TAG, "RemoteException in reportLocation: ", e);
         }
     }
+    
+    /**
+     * Used by NetInitiatedActivity to report user response
+     * for network initiated GPS fix requests.
+     *
+     * {@hide}
+     */
+    public boolean sendNiResponse(int notifId, int userResponse) {
+    	try {
+            return mService.sendNiResponse(notifId, userResponse);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in sendNiResponse: ", e);
+            return false;
+        }
+    }
+ 
 }
diff --git a/location/java/com/android/internal/location/GpsLocationProvider.java b/location/java/com/android/internal/location/GpsLocationProvider.java
index 0b4fb88..bfa0671 100755
--- a/location/java/com/android/internal/location/GpsLocationProvider.java
+++ b/location/java/com/android/internal/location/GpsLocationProvider.java
@@ -27,6 +27,7 @@
 import android.location.IGpsStatusProvider;
 import android.location.ILocationManager;
 import android.location.ILocationProvider;
+import android.location.INetInitiatedListener;
 import android.location.Location;
 import android.location.LocationManager;
 import android.location.LocationProvider;
@@ -46,14 +47,18 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.location.GpsNetInitiatedHandler;
+import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.StringBufferInputStream;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Properties;
+import java.util.Map.Entry;
 
 /**
  * A GPS implementation of LocationProvider used by LocationManager.
@@ -214,6 +219,7 @@
     private String mAGpsApn;
     private int mAGpsDataConnectionState;
     private final ConnectivityManager mConnMgr;
+    private final GpsNetInitiatedHandler mNIHandler; 
 
     // Wakelocks
     private final static String WAKELOCK_KEY = "GpsLocationProvider";
@@ -324,6 +330,7 @@
     public GpsLocationProvider(Context context, ILocationManager locationManager) {
         mContext = context;
         mLocationManager = locationManager;
+        mNIHandler= new GpsNetInitiatedHandler(context, this);
 
         // Create a wake lock
         PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -1047,6 +1054,96 @@
         }
     }
 
+    //=============================================================
+    // NI Client support
+	//=============================================================
+    private final INetInitiatedListener mNetInitiatedListener = new INetInitiatedListener.Stub() {
+    	// Sends a response for an NI reqeust to HAL.
+    	public boolean sendNiResponse(int notificationId, int userResponse)
+    	{
+        	// TODO Add Permission check
+    		
+    		StringBuilder extrasBuf = new StringBuilder();
+
+    		if (Config.LOGD) Log.d(TAG, "sendNiResponse, notifId: " + notificationId +
+    				", response: " + userResponse);
+    		
+    		native_send_ni_response(notificationId, userResponse);
+    		
+    		return true;
+    	}        
+    };
+        
+    public INetInitiatedListener getNetInitiatedListener() {
+        return mNetInitiatedListener;
+    }
+
+    // Called by JNI function to report an NI request.
+	@SuppressWarnings("deprecation")
+	public void reportNiNotification(
+        	int notificationId,
+        	int niType,
+        	int notifyFlags,
+        	int timeout,
+        	int defaultResponse,
+        	String requestorId,
+        	String text,
+        	int requestorIdEncoding,
+        	int textEncoding,
+        	String extras  // Encoded extra data
+        )
+	{
+		Log.i(TAG, "reportNiNotification: entered");
+		Log.i(TAG, "notificationId: " + notificationId +
+				", niType: " + niType +
+				", notifyFlags: " + notifyFlags +
+				", timeout: " + timeout +
+				", defaultResponse: " + defaultResponse);
+		
+		Log.i(TAG, "requestorId: " + requestorId +
+				", text: " + text +
+				", requestorIdEncoding: " + requestorIdEncoding +
+				", textEncoding: " + textEncoding);
+		
+		GpsNiNotification notification = new GpsNiNotification();
+		
+		notification.notificationId = notificationId;
+		notification.niType = niType;
+		notification.needNotify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_NOTIFY) != 0;
+		notification.needVerify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_VERIFY) != 0;
+		notification.privacyOverride = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_PRIVACY_OVERRIDE) != 0;
+		notification.timeout = timeout;
+		notification.defaultResponse = defaultResponse;
+		notification.requestorId = requestorId;
+		notification.text = text;
+		notification.requestorIdEncoding = requestorIdEncoding;
+		notification.textEncoding = textEncoding;
+		
+		// Process extras, assuming the format is
+		// one of more lines of "key = value"
+		Bundle bundle = new Bundle();
+		
+		if (extras == null) extras = "";
+		Properties extraProp = new Properties();
+		
+		try {
+			extraProp.load(new StringBufferInputStream(extras));
+		}
+		catch (IOException e)
+		{
+			Log.e(TAG, "reportNiNotification cannot parse extras data: " + extras);
+		}
+		
+		for (Entry<Object, Object> ent : extraProp.entrySet())
+		{
+			bundle.putString((String) ent.getKey(), (String) ent.getValue());
+		}		
+		
+		notification.extras = bundle;
+		
+		mNIHandler.handleNiNotification(notification);		
+	}
+
     private class GpsEventThread extends Thread {
 
         public GpsEventThread() {
@@ -1252,4 +1349,7 @@
     private native void native_agps_data_conn_closed();
     private native void native_agps_data_conn_failed();
     private native void native_set_agps_server(int type, String hostname, int port);
+
+    // Network-initiated (NI) Support
+    private native void native_send_ni_response(int notificationId, int userResponse);
 }
diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
new file mode 100755
index 0000000..a5466d1
--- /dev/null
+++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
@@ -0,0 +1,457 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.location;
+
+import java.io.UnsupportedEncodingException;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A GPS Network-initiated Handler class used by LocationManager.
+ *
+ * {@hide}
+ */
+public class GpsNetInitiatedHandler {
+
+    private static final String TAG = "GpsNetInitiatedHandler";
+
+    private static final boolean DEBUG = true;
+    private static final boolean VERBOSE = false;
+
+    // NI verify activity for bringing up UI (not used yet)
+    public static final String ACTION_NI_VERIFY = "android.intent.action.NETWORK_INITIATED_VERIFY";
+    
+    // string constants for defining data fields in NI Intent
+    public static final String NI_INTENT_KEY_NOTIF_ID = "notif_id";
+    public static final String NI_INTENT_KEY_TITLE = "title";
+    public static final String NI_INTENT_KEY_MESSAGE = "message";
+    public static final String NI_INTENT_KEY_TIMEOUT = "timeout";
+    public static final String NI_INTENT_KEY_DEFAULT_RESPONSE = "default_resp";
+    
+    // the extra command to send NI response to GpsLocationProvider
+    public static final String NI_RESPONSE_EXTRA_CMD = "send_ni_response";
+    
+    // the extra command parameter names in the Bundle
+    public static final String NI_EXTRA_CMD_NOTIF_ID = "notif_id";
+    public static final String NI_EXTRA_CMD_RESPONSE = "response";
+    
+    // these need to match GpsNiType constants in gps_ni.h
+    public static final int GPS_NI_TYPE_VOICE = 1;
+    public static final int GPS_NI_TYPE_UMTS_SUPL = 2;
+    public static final int GPS_NI_TYPE_UMTS_CTRL_PLANE = 3;
+    
+    // these need to match GpsUserResponseType constants in gps_ni.h    
+    public static final int GPS_NI_RESPONSE_ACCEPT = 1;
+    public static final int GPS_NI_RESPONSE_DENY = 2;
+    public static final int GPS_NI_RESPONSE_NORESP = 3;    
+    
+    // these need to match GpsNiNotifyFlags constants in gps_ni.h
+    public static final int GPS_NI_NEED_NOTIFY = 0x0001;
+    public static final int GPS_NI_NEED_VERIFY = 0x0002;
+    public static final int GPS_NI_PRIVACY_OVERRIDE = 0x0004;
+    
+    // these need to match GpsNiEncodingType in gps_ni.h
+    public static final int GPS_ENC_NONE = 0;
+    public static final int GPS_ENC_SUPL_GSM_DEFAULT = 1;
+    public static final int GPS_ENC_SUPL_UTF8 = 2;
+    public static final int GPS_ENC_SUPL_UCS2 = 3;
+    public static final int GPS_ENC_UNKNOWN = -1;
+    
+    private final Context mContext;
+    
+    // parent gps location provider
+    private final GpsLocationProvider mGpsLocationProvider;
+    
+    // configuration of notificaiton behavior
+    private boolean mPlaySounds = false;
+    private boolean visible = true;
+    private boolean mPopupImmediately = true;
+    
+    // Set to true if string from HAL is encoded as Hex, e.g., "3F0039"    
+    static private boolean mIsHexInput = true;
+        
+    public static class GpsNiNotification
+    {
+    	int notificationId;
+    	int niType;
+    	boolean needNotify;
+    	boolean needVerify;
+    	boolean privacyOverride;
+    	int timeout;
+    	int defaultResponse;
+    	String requestorId;
+    	String text;
+    	int requestorIdEncoding;
+    	int textEncoding;
+    	Bundle extras;
+    };
+    
+    public static class GpsNiResponse {
+    	/* User reponse, one of the values in GpsUserResponseType */
+    	int userResponse;
+    	/* Optional extra data to pass with the user response */
+    	Bundle extras;
+    };
+    
+    /**
+     * The notification that is shown when a network-initiated notification
+     * (and verification) event is received. 
+     * <p>
+     * This is lazily created, so use {@link #setNINotification()}.
+     */
+    private Notification mNiNotification;
+    
+    public GpsNetInitiatedHandler(Context context, GpsLocationProvider gpsLocationProvider) {
+    	mContext = context;       
+    	mGpsLocationProvider = gpsLocationProvider;
+    }
+    
+    // Handles NI events from HAL
+    public void handleNiNotification(GpsNiNotification notif)
+    {
+    	if (DEBUG) Log.d(TAG, "handleNiNotification" + " notificationId: " + notif.notificationId 
+    			+ " requestorId: " + notif.requestorId + " text: " + notif.text);
+    	
+    	// Notify and verify with immediate pop-up
+    	if (notif.needNotify && notif.needVerify && mPopupImmediately)
+    	{
+    		// Popup the dialog box now
+    		openNiDialog(notif);
+    	}
+    	
+    	// Notify only, or delayed pop-up (change mPopupImmediately to FALSE) 
+    	if (notif.needNotify && !notif.needVerify ||
+    		notif.needNotify && notif.needVerify && !mPopupImmediately) 
+    	{
+    		// Show the notification
+
+    		// if mPopupImmediately == FALSE and needVerify == TRUE, a dialog will be opened
+    		// when the user opens the notification message
+    		
+    		setNiNotification(notif);
+    	}
+    	
+    	// ACCEPT cases: 1. Notify, no verify; 2. no notify, no verify; 3. privacy override.
+    	if ( notif.needNotify && !notif.needVerify || 
+    		!notif.needNotify && !notif.needVerify || 
+    		 notif.privacyOverride)
+    	{
+    		try {
+    			mGpsLocationProvider.getNetInitiatedListener().sendNiResponse(notif.notificationId, GPS_NI_RESPONSE_ACCEPT);
+    		} 
+    		catch (RemoteException e)
+    		{
+    			Log.e(TAG, e.getMessage());
+    		}
+    	}
+    	
+    	//////////////////////////////////////////////////////////////////////////
+    	//   A note about timeout
+    	//   According to the protocol, in the need_notify and need_verify case,
+    	//   a default response should be sent when time out.
+    	//   
+    	//   In some GPS hardware, the GPS driver (under HAL) can handle the timeout case
+    	//   and this class GpsNetInitiatedHandler does not need to do anything.
+    	//   
+    	//   However, the UI should at least close the dialog when timeout. Further, 
+    	//   for more general handling, timeout response should be added to the Handler here.
+    	//    	    	
+    }
+    
+    // Sets the NI notification.
+    private synchronized void setNiNotification(GpsNiNotification notif) {
+        NotificationManager notificationManager = (NotificationManager) mContext
+                .getSystemService(Context.NOTIFICATION_SERVICE);
+        if (notificationManager == null) {
+            return;
+        }
+      
+    	String title = getNotifTitle(notif);
+    	String message = getNotifMessage(notif);
+        
+        if (DEBUG) Log.d(TAG, "setNiNotification, notifyId: " + notif.notificationId +
+        		", title: " + title +
+        		", message: " + message);
+        
+    	// Construct Notification
+    	if (mNiNotification == null) {
+        	mNiNotification = new Notification();
+        	mNiNotification.icon = com.android.internal.R.drawable.stat_sys_gps_on; /* Change notification icon here */
+        	mNiNotification.when = 0;
+        }
+    	
+        if (mPlaySounds) {
+        	mNiNotification.defaults |= Notification.DEFAULT_SOUND;
+        } else {
+        	mNiNotification.defaults &= ~Notification.DEFAULT_SOUND;
+        }        
+        
+        mNiNotification.flags = Notification.FLAG_ONGOING_EVENT;
+        mNiNotification.tickerText = getNotifTicker(notif);
+        
+        // if not to popup dialog immediately, pending intent will open the dialog
+        Intent intent = !mPopupImmediately ? getDlgIntent(notif) : new Intent();    	        
+        PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 0);                
+        mNiNotification.setLatestEventInfo(mContext, title, message, pi);
+        
+        if (visible) {
+            notificationManager.notify(notif.notificationId, mNiNotification);
+        } else {
+            notificationManager.cancel(notif.notificationId);
+        }
+    }
+    
+    // Opens the notification dialog and waits for user input
+    private void openNiDialog(GpsNiNotification notif) 
+    {
+    	Intent intent = getDlgIntent(notif);
+    	
+    	if (DEBUG) Log.d(TAG, "openNiDialog, notifyId: " + notif.notificationId + 
+    			", requestorId: " + notif.requestorId + 
+    			", text: " + notif.text);               	
+
+    	mContext.startActivity(intent);
+    }
+    
+    // Construct the intent for bringing up the dialog activity, which shows the 
+    // notification and takes user input
+    private Intent getDlgIntent(GpsNiNotification notif)
+    {
+    	Intent intent = new Intent();
+    	String title = getDialogTitle(notif);
+    	String message = getDialogMessage(notif);
+    	
+    	// directly bring up the NI activity
+    	intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+    	intent.setClass(mContext, com.android.internal.app.NetInitiatedActivity.class);    	
+
+    	// put data in the intent
+    	intent.putExtra(NI_INTENT_KEY_NOTIF_ID, notif.notificationId);    	
+    	intent.putExtra(NI_INTENT_KEY_TITLE, title);
+    	intent.putExtra(NI_INTENT_KEY_MESSAGE, message);
+    	intent.putExtra(NI_INTENT_KEY_TIMEOUT, notif.timeout);
+    	intent.putExtra(NI_INTENT_KEY_DEFAULT_RESPONSE, notif.defaultResponse);
+    	
+    	if (DEBUG) Log.d(TAG, "generateIntent, title: " + title + ", message: " + message +
+    			", timeout: " + notif.timeout);
+    	
+    	return intent;
+    }
+    
+    // Converts a string (or Hex string) to a char array
+    static byte[] stringToByteArray(String original, boolean isHex)
+    {
+    	int length = isHex ? original.length() / 2 : original.length();
+    	byte[] output = new byte[length];
+    	int i;
+    	
+    	if (isHex)
+    	{
+    		for (i = 0; i < length; i++)
+    		{
+    			output[i] = (byte) Integer.parseInt(original.substring(i*2, i*2+2), 16);
+    		}
+    	}
+    	else {
+    		for (i = 0; i < length; i++)
+    		{
+    			output[i] = (byte) original.charAt(i);
+    		}
+    	}
+    	
+    	return output;
+    }
+    
+    /**
+     * Unpacks an byte array containing 7-bit packed characters into a String.
+     * 
+     * @param input a 7-bit packed char array
+     * @return the unpacked String
+     */
+    static String decodeGSMPackedString(byte[] input)
+    {
+    	final char CHAR_CR = 0x0D;
+    	int nStridx = 0;
+    	int nPckidx = 0;
+    	int num_bytes = input.length;
+    	int cPrev = 0;
+    	int cCurr = 0;
+    	byte nShift;
+    	byte nextChar;
+    	byte[] stringBuf = new byte[input.length * 2]; 
+    	String result = "";
+    	
+    	while(nPckidx < num_bytes)
+    	{
+    		nShift = (byte) (nStridx & 0x07);
+    		cCurr = input[nPckidx++];
+    		if (cCurr < 0) cCurr += 256;
+
+    		/* A 7-bit character can be split at the most between two bytes of packed
+    		 ** data.
+    		 */
+    		nextChar = (byte) (( (cCurr << nShift) | (cPrev >> (8-nShift)) ) & 0x7F);
+    		stringBuf[nStridx++] = nextChar;
+
+    		/* Special case where the whole of the next 7-bit character fits inside
+    		 ** the current byte of packed data.
+    		 */
+    		if(nShift == 6)
+    		{
+    			/* If the next 7-bit character is a CR (0x0D) and it is the last
+    			 ** character, then it indicates a padding character. Drop it.
+    			 */
+    			if (nPckidx == num_bytes || (cCurr >> 1) == CHAR_CR)
+    			{
+    				break;
+    			}
+    			
+    			nextChar = (byte) (cCurr >> 1); 
+    			stringBuf[nStridx++] = nextChar;
+    		}
+
+    		cPrev = cCurr;
+    	}
+    	
+    	try{
+    		result = new String(stringBuf, 0, nStridx, "US-ASCII");
+    	}
+    	catch (UnsupportedEncodingException e)
+    	{
+    		Log.e(TAG, e.getMessage());
+    	}
+    	
+    	return result;
+    }
+    
+    static String decodeUTF8String(byte[] input)
+    {
+    	String decoded = "";
+    	try {
+    		decoded = new String(input, "UTF-8");
+    	}
+    	catch (UnsupportedEncodingException e)
+    	{ 
+    		Log.e(TAG, e.getMessage());
+    	} 
+		return decoded;
+    }
+    
+    static String decodeUCS2String(byte[] input)
+    {
+    	String decoded = "";
+    	try {
+    		decoded = new String(input, "UTF-16");
+    	}
+    	catch (UnsupportedEncodingException e)
+    	{ 
+    		Log.e(TAG, e.getMessage());
+    	} 
+		return decoded;
+    }
+    
+    /** Decode NI string
+     * 
+     * @param original   The text string to be decoded
+     * @param isHex      Specifies whether the content of the string has been encoded as a Hex string. Encoding
+     *                   a string as Hex can allow zeros inside the coded text. 
+     * @param coding     Specifies the coding scheme of the string, such as GSM, UTF8, UCS2, etc. This coding scheme
+     * 					 needs to match those used passed to HAL from the native GPS driver. Decoding is done according
+     *                   to the <code> coding </code>, after a Hex string is decoded. Generally, if the
+     *                   notification strings don't need further decoding, <code> coding </code> encoding can be 
+     *                   set to -1, and <code> isHex </code> can be false.
+     * @return the decoded string
+     */
+    static private String decodeString(String original, boolean isHex, int coding)
+    {
+    	String decoded = original;
+    	byte[] input = stringToByteArray(original, isHex);
+
+    	switch (coding) {
+    	case GPS_ENC_NONE:
+    		decoded = original;
+    		break;
+    		
+    	case GPS_ENC_SUPL_GSM_DEFAULT:
+    		decoded = decodeGSMPackedString(input);
+    		break;
+    		
+    	case GPS_ENC_SUPL_UTF8:
+    		decoded = decodeUTF8String(input);
+    		break;
+    		
+    	case GPS_ENC_SUPL_UCS2:
+    		decoded = decodeUCS2String(input);
+    		break;
+    		
+    	case GPS_ENC_UNKNOWN:
+    		decoded = original;
+    		break;
+    		
+    	default:
+    		Log.e(TAG, "Unknown encoding " + coding + " for NI text " + original);
+    		break;
+    	}
+    	return decoded;
+    }
+    
+    // change this to configure notification display
+    static private String getNotifTicker(GpsNiNotification notif)
+    {
+    	String ticker = String.format("Position request! ReqId: [%s] ClientName: [%s]",
+    			decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding),
+    			decodeString(notif.text, mIsHexInput, notif.textEncoding));
+    	return ticker;
+    }
+    
+    // change this to configure notification display
+    static private String getNotifTitle(GpsNiNotification notif)
+    {
+    	String title = String.format("Position Request");
+    	return title;
+    }
+    
+    // change this to configure notification display
+    static private String getNotifMessage(GpsNiNotification notif)
+    {
+    	String message = String.format(
+    			"NI Request received from [%s] for client [%s]!", 
+    			decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding),
+    			decodeString(notif.text, mIsHexInput, notif.textEncoding));
+    	return message;
+    }       
+    
+    // change this to configure dialog display (for verification)
+    static public String getDialogTitle(GpsNiNotification notif)
+    {
+    	return getNotifTitle(notif);
+    }
+    
+    // change this to configure dialog display (for verification)
+    static private String getDialogMessage(GpsNiNotification notif)
+    {
+    	return getNotifMessage(notif);
+    }
+    
+}
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index c65c2be..cb6c168 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -49,6 +49,7 @@
 import android.location.ILocationListener;
 import android.location.ILocationManager;
 import android.location.ILocationProvider;
+import android.location.INetInitiatedListener;
 import android.location.Location;
 import android.location.LocationManager;
 import android.location.LocationProvider;
@@ -71,6 +72,7 @@
 import com.android.internal.location.GpsLocationProvider;
 import com.android.internal.location.LocationProviderProxy;
 import com.android.internal.location.MockProvider;
+import com.android.internal.location.GpsNetInitiatedHandler;
 
 /**
  * The service class that manages LocationProviders and issues location
@@ -118,6 +120,7 @@
     private final Context mContext;
     private IGeocodeProvider mGeocodeProvider;
     private IGpsStatusProvider mGpsStatusProvider;
+    private INetInitiatedListener mNetInitiatedListener;
     private LocationWorkerHandler mLocationHandler;
 
     // Cache the real providers for use in addTestProvider() and removeTestProvider()
@@ -541,6 +544,7 @@
             // Create a gps location provider
             GpsLocationProvider provider = new GpsLocationProvider(mContext, this);
             mGpsStatusProvider = provider.getGpsStatusProvider();
+            mNetInitiatedListener = provider.getNetInitiatedListener();
             LocationProviderProxy proxy = new LocationProviderProxy(LocationManager.GPS_PROVIDER, provider);
             addProvider(proxy);
             mGpsLocationProvider = proxy;
@@ -1131,6 +1135,18 @@
         }
     }
 
+    public boolean sendNiResponse(int notifId, int userResponse)
+    {
+        try {
+            return mNetInitiatedListener.sendNiResponse(notifId, userResponse);
+        }
+        catch (RemoteException e)
+        {
+            Log.e(TAG, "RemoteException in LocationManagerService.sendNiResponse");
+            return false;
+        }
+    }
+
     class ProximityAlert {
         final int  mUid;
         final double mLatitude;