diff options
-rw-r--r-- | Android.mk | 1 | ||||
-rwxr-xr-x | core/java/com/android/internal/app/NetInitiatedActivity.java | 139 | ||||
-rwxr-xr-x | core/jni/android_location_GpsLocationProvider.cpp | 85 | ||||
-rw-r--r-- | core/res/AndroidManifest.xml | 4 | ||||
-rw-r--r-- | location/java/android/location/ILocationManager.aidl | 3 | ||||
-rwxr-xr-x | location/java/android/location/INetInitiatedListener.aidl | 26 | ||||
-rw-r--r-- | location/java/android/location/LocationManager.java | 16 | ||||
-rwxr-xr-x | location/java/com/android/internal/location/GpsLocationProvider.java | 100 | ||||
-rwxr-xr-x | location/java/com/android/internal/location/GpsNetInitiatedHandler.java | 457 | ||||
-rw-r--r-- | services/java/com/android/server/LocationManagerService.java | 16 |
10 files changed, 836 insertions, 11 deletions
@@ -147,6 +147,7 @@ LOCAL_SRC_FILES += \ 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_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; // 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 GpsStatus sGpsStatusCopy; static GpsSvStatus sGpsSvStatusCopy; static AGpsStatus sAGpsStatusCopy; static NmeaSentence sNmeaBufferCopy[NMEA_SENTENCE_LENGTH]; +static GpsNiNotification sGpsNiNotificationCopy; enum CallbackType { kLocation = 1, @@ -71,6 +77,7 @@ enum CallbackType { kXtraDownloadRequest = 16, kDisableRequest = 32, kNmeaAvailable = 64, + kNiNotification = 128, }; static int sPendingCallbacks; @@ -160,6 +167,20 @@ download_request_callback() 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 @@ 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"); @@ -175,6 +200,7 @@ static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env, 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 @@ static jboolean android_location_GpsLocationProvider_init(JNIEnv* env, jobject o 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 @@ static void android_location_GpsLocationProvider_cleanup(JNIEnv* env, jobject ob 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 @@ static void android_location_GpsLocationProvider_wait_for_event(JNIEnv* env, job { pthread_mutex_lock(&sEventMutex); pthread_cond_wait(&sEventCond, &sEventMutex); - + // copy and clear the callback flags int pendingCallbacks = sPendingCallbacks; sPendingCallbacks = 0; @@ -254,18 +286,20 @@ static void android_location_GpsLocationProvider_wait_for_event(JNIEnv* env, job 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 @@ static void android_location_GpsLocationProvider_wait_for_event(JNIEnv* env, job 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 @@ static jboolean android_location_GpsLocationProvider_supports_xtra(JNIEnv* env, 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_set_agps_server(JNIEnv* env, jo } } +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 @@ static JNINativeMethod sMethods[] = { {"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 @@ interface ILocationManager /* for installing external Location Providers */ void installLocationProvider(String name, ILocationProvider provider); void installGeocodeProvider(IGeocodeProvider provider); + + // for NI support + boolean sendNiResponse(int notifId, int userResponse); } diff --git a/location/java/android/location/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 @@ public class LocationManager { Log.e(TAG, "RemoteException in reportLocation: ", e); } } + + /** + * Used by NetInitiatedActivity to report user response + * for network initiated GPS fix requests. + * + * {@hide} + */ + public boolean sendNiResponse(int notifId, int userResponse) { + try { + return mService.sendNiResponse(notifId, userResponse); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in sendNiResponse: ", e); + return false; + } + } + } diff --git a/location/java/com/android/internal/location/GpsLocationProvider.java b/location/java/com/android/internal/location/GpsLocationProvider.java index 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.IGpsStatusListener; import android.location.IGpsStatusProvider; import android.location.ILocationManager; import android.location.ILocationProvider; +import android.location.INetInitiatedListener; import android.location.Location; import android.location.LocationManager; import android.location.LocationProvider; @@ -46,14 +47,18 @@ import android.util.SparseIntArray; import com.android.internal.app.IBatteryStats; import com.android.internal.telephony.Phone; import com.android.internal.telephony.TelephonyIntents; +import com.android.internal.location.GpsNetInitiatedHandler; +import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.io.StringBufferInputStream; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Properties; +import java.util.Map.Entry; /** * A GPS implementation of LocationProvider used by LocationManager. @@ -214,6 +219,7 @@ public class GpsLocationProvider extends ILocationProvider.Stub { private String mAGpsApn; private int mAGpsDataConnectionState; private final ConnectivityManager mConnMgr; + private final GpsNetInitiatedHandler mNIHandler; // Wakelocks private final static String WAKELOCK_KEY = "GpsLocationProvider"; @@ -324,6 +330,7 @@ public class GpsLocationProvider extends ILocationProvider.Stub { public GpsLocationProvider(Context context, ILocationManager locationManager) { mContext = context; mLocationManager = locationManager; + mNIHandler= new GpsNetInitiatedHandler(context, this); // Create a wake lock PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); @@ -1047,6 +1054,96 @@ public class GpsLocationProvider extends ILocationProvider.Stub { } } + //============================================================= + // NI Client support + //============================================================= + private final INetInitiatedListener mNetInitiatedListener = new INetInitiatedListener.Stub() { + // Sends a response for an NI reqeust to HAL. + public boolean sendNiResponse(int notificationId, int userResponse) + { + // TODO Add Permission check + + StringBuilder extrasBuf = new StringBuilder(); + + if (Config.LOGD) Log.d(TAG, "sendNiResponse, notifId: " + notificationId + + ", response: " + userResponse); + + native_send_ni_response(notificationId, userResponse); + + return true; + } + }; + + public INetInitiatedListener getNetInitiatedListener() { + return mNetInitiatedListener; + } + + // Called by JNI function to report an NI request. + @SuppressWarnings("deprecation") + public void reportNiNotification( + int notificationId, + int niType, + int notifyFlags, + int timeout, + int defaultResponse, + String requestorId, + String text, + int requestorIdEncoding, + int textEncoding, + String extras // Encoded extra data + ) + { + Log.i(TAG, "reportNiNotification: entered"); + Log.i(TAG, "notificationId: " + notificationId + + ", niType: " + niType + + ", notifyFlags: " + notifyFlags + + ", timeout: " + timeout + + ", defaultResponse: " + defaultResponse); + + Log.i(TAG, "requestorId: " + requestorId + + ", text: " + text + + ", requestorIdEncoding: " + requestorIdEncoding + + ", textEncoding: " + textEncoding); + + GpsNiNotification notification = new GpsNiNotification(); + + notification.notificationId = notificationId; + notification.niType = niType; + notification.needNotify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_NOTIFY) != 0; + notification.needVerify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_VERIFY) != 0; + notification.privacyOverride = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_PRIVACY_OVERRIDE) != 0; + notification.timeout = timeout; + notification.defaultResponse = defaultResponse; + notification.requestorId = requestorId; + notification.text = text; + notification.requestorIdEncoding = requestorIdEncoding; + notification.textEncoding = textEncoding; + + // Process extras, assuming the format is + // one of more lines of "key = value" + Bundle bundle = new Bundle(); + + if (extras == null) extras = ""; + Properties extraProp = new Properties(); + + try { + extraProp.load(new StringBufferInputStream(extras)); + } + catch (IOException e) + { + Log.e(TAG, "reportNiNotification cannot parse extras data: " + extras); + } + + for (Entry<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 @@ public class GpsLocationProvider extends ILocationProvider.Stub { private native void native_agps_data_conn_closed(); private native void native_agps_data_conn_failed(); private native void native_set_agps_server(int type, String hostname, int port); + + // Network-initiated (NI) Support + private native void native_send_ni_response(int notificationId, int userResponse); } diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java new file mode 100755 index 0000000..a5466d1 --- /dev/null +++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java @@ -0,0 +1,457 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.location; + +import java.io.UnsupportedEncodingException; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.RemoteException; +import android.util.Log; + +/** + * A GPS Network-initiated Handler class used by LocationManager. + * + * {@hide} + */ +public class GpsNetInitiatedHandler { + + private static final String TAG = "GpsNetInitiatedHandler"; + + private static final boolean DEBUG = true; + private static final boolean VERBOSE = false; + + // NI verify activity for bringing up UI (not used yet) + public static final String ACTION_NI_VERIFY = "android.intent.action.NETWORK_INITIATED_VERIFY"; + + // string constants for defining data fields in NI Intent + public static final String NI_INTENT_KEY_NOTIF_ID = "notif_id"; + public static final String NI_INTENT_KEY_TITLE = "title"; + public static final String NI_INTENT_KEY_MESSAGE = "message"; + public static final String NI_INTENT_KEY_TIMEOUT = "timeout"; + public static final String NI_INTENT_KEY_DEFAULT_RESPONSE = "default_resp"; + + // the extra command to send NI response to GpsLocationProvider + public static final String NI_RESPONSE_EXTRA_CMD = "send_ni_response"; + + // the extra command parameter names in the Bundle + public static final String NI_EXTRA_CMD_NOTIF_ID = "notif_id"; + public static final String NI_EXTRA_CMD_RESPONSE = "response"; + + // these need to match GpsNiType constants in gps_ni.h + public static final int GPS_NI_TYPE_VOICE = 1; + public static final int GPS_NI_TYPE_UMTS_SUPL = 2; + public static final int GPS_NI_TYPE_UMTS_CTRL_PLANE = 3; + + // these need to match GpsUserResponseType constants in gps_ni.h + public static final int GPS_NI_RESPONSE_ACCEPT = 1; + public static final int GPS_NI_RESPONSE_DENY = 2; + public static final int GPS_NI_RESPONSE_NORESP = 3; + + // these need to match GpsNiNotifyFlags constants in gps_ni.h + public static final int GPS_NI_NEED_NOTIFY = 0x0001; + public static final int GPS_NI_NEED_VERIFY = 0x0002; + public static final int GPS_NI_PRIVACY_OVERRIDE = 0x0004; + + // these need to match GpsNiEncodingType in gps_ni.h + public static final int GPS_ENC_NONE = 0; + public static final int GPS_ENC_SUPL_GSM_DEFAULT = 1; + public static final int GPS_ENC_SUPL_UTF8 = 2; + public static final int GPS_ENC_SUPL_UCS2 = 3; + public static final int GPS_ENC_UNKNOWN = -1; + + private final Context mContext; + + // parent gps location provider + private final GpsLocationProvider mGpsLocationProvider; + + // configuration of notificaiton behavior + private boolean mPlaySounds = false; + private boolean visible = true; + private boolean mPopupImmediately = true; + + // Set to true if string from HAL is encoded as Hex, e.g., "3F0039" + static private boolean mIsHexInput = true; + + public static class GpsNiNotification + { + int notificationId; + int niType; + boolean needNotify; + boolean needVerify; + boolean privacyOverride; + int timeout; + int defaultResponse; + String requestorId; + String text; + int requestorIdEncoding; + int textEncoding; + Bundle extras; + }; + + public static class GpsNiResponse { + /* User reponse, one of the values in GpsUserResponseType */ + int userResponse; + /* Optional extra data to pass with the user response */ + Bundle extras; + }; + + /** + * The notification that is shown when a network-initiated notification + * (and verification) event is received. + * <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.IGpsStatusProvider; 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 android.util.PrintWriterPrinter; 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 @@ public class LocationManagerService extends ILocationManager.Stub implements Run 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 @@ public class LocationManagerService extends ILocationManager.Stub implements Run // 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 class LocationManagerService extends ILocationManager.Stub implements Run } } + 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; |