Bug 1201778 - [GPS-SUPL NI] Support for displaying NI message to user. r=kanru

This commit is contained in:
Alphan Chen 2016-01-15 10:33:59 +08:00
parent 68479256ca
commit 2a1cc8f6ef
8 changed files with 410 additions and 28 deletions

View File

@ -1141,7 +1141,7 @@ window.addEventListener('ContentStart', function update_onContentStart() {
Services.obs.addObserver(function(aSubject, aTopic, aData) {
shell.sendChromeEvent({
type: 'supl-notification',
id: aData
data: aData
});
}, "supl-ni-notify", false);
})();
@ -1150,7 +1150,7 @@ window.addEventListener('ContentStart', function update_onContentStart() {
Services.obs.addObserver(function(aSubject, aTopic, aData) {
shell.sendChromeEvent({
type: 'supl-verification',
id: aData
data: aData
});
}, "supl-ni-verify", false);
})();
@ -1159,7 +1159,7 @@ window.addEventListener('ContentStart', function update_onContentStart() {
Services.obs.addObserver(function(aSubject, aTopic, aData) {
shell.sendChromeEvent({
type: 'supl-verification-timeout',
id: aData
data: aData
});
}, "supl-ni-verify-timeout", false);
})();

View File

@ -4,7 +4,12 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <cmath>
#include "GeolocationUtil.h"
#include "nsContentUtils.h"
namespace mozilla {
double CalculateDeltaInMeter(double aLat, double aLon, double aLastLat, double aLastLon)
{
@ -26,3 +31,188 @@ double CalculateDeltaInMeter(double aLat, double aLon, double aLastLat, double a
return acos(cosDelta) * 6378137;
}
bool
DecodeGsmDefaultToString(UniquePtr<char[]> &aMessage, nsString &aWideStr)
{
if (!aMessage || !aMessage.get()) {
return false;
}
int lengthBytes = strlen(aMessage.get());
if (lengthBytes <= 0) {
return false;
}
int lengthSeptets = (lengthBytes*8)/7;
if (lengthBytes % 7 == 0) {
char PADDING_CHAR = 0x00;
if(aMessage[lengthBytes-1] >> 1 == PADDING_CHAR) {
lengthSeptets -=1;
}
}
bool prevCharWasEscape = false;
nsString languageTableToChar = NS_LITERAL_STRING(
/**
* National Language Identifier: 0x00
* 6.2.1 GSM 7 bit Default Alphabet
*/
// 01.....23.....4.....5.....6.....7.....8.....9.....A.B.....C.....D.E.....F.....
"@\u00a3$\u00a5\u00e8\u00e9\u00f9\u00ec\u00f2\u00c7\n\u00d8\u00f8\r\u00c5\u00e5"
//0.....12.....3.....4.....5.....6.....7.....8.....9.....A.....BC.....D.....E.....F.....
"\u0394_\u03a6\u0393\u039b\u03a9\u03a0\u03a8\u03a3\u0398\u039e?\u00c6\u00e6\u00df\u00c9"
// 012.34.....56789ABCDEF
" !\"#\u00a4%&'()*+,-./"
// 0123456789ABCDEF
"0123456789:;<=>?"
// 0.....123456789ABCDEF
"\u00a1ABCDEFGHIJKLMNO"
// 0123456789AB.....C.....D.....E.....F.....
"PQRSTUVWXYZ\u00c4\u00d6\u00d1\u00dc\u00a7"
// 0.....123456789ABCDEF
"\u00bfabcdefghijklmno"
// 0123456789AB.....C.....D.....E.....F.....
"pqrstuvwxyz\u00e4\u00f6\u00f1\u00fc\u00e0");
nsString shiftTableToChar = NS_LITERAL_STRING(
/**
* National Language Identifier: 0x00
* 6.2.1.1 GSM 7 bit default alphabet extension table
*/
// 0123456789A.....BCD.....EF
" \u000c \ufffe "
// 0123456789AB.....CDEF
" ^ \uffff "
// 0123456789ABCDEF.
" {} \\"
// 0123456789ABCDEF
" [~] "
// 0123456789ABCDEF
"| "
// 0123456789ABCDEF
" "
// 012345.....6789ABCDEF
" \u20ac "
// 0123456789ABCDEF
" ");
nsString output;
for (int i = 0; i < lengthSeptets ; i++) {
int bitOffset = 7 * i;
int byteOffset = bitOffset / 8;
int shift = bitOffset % 8;
int gsmVal;
gsmVal = 0x7f & (aMessage[byteOffset] >> shift);
if (shift > 1) {
//set msb bits to 0
gsmVal &= 0x7f >> (shift-1);
gsmVal |= 0x7f & (aMessage[byteOffset+1] << (8-shift));
}
const char GSM_EXTENDED_ESCAPE = 0x1B;
if(prevCharWasEscape) {
if (gsmVal == GSM_EXTENDED_ESCAPE) {
output.AppendLiteral(" ");
} else {
char16_t c = shiftTableToChar.CharAt(gsmVal);
if (c == MOZ_UTF16(' ')) {
c = languageTableToChar.CharAt(gsmVal);
}
output += c;
}
prevCharWasEscape = false;
} else if(gsmVal == GSM_EXTENDED_ESCAPE) {
prevCharWasEscape = true;
} else {
char16_t c = languageTableToChar.CharAt(gsmVal);
output += c;
}
}
aWideStr = output;
return true;
}
nsString
DecodeNIString(const char* aMessage, GpsNiEncodingType aEncodingType)
{
nsString wide_str;
bool little_endian = true;
int starthexIndex = 0, byteIndex = 0;
// convert to byte string first
int hexstring_length = strlen(aMessage);
// Note: hexstring_length cannot be an odd number.
if ((hexstring_length == 0) || (hexstring_length % 2 != 0)) {
nsContentUtils::LogMessageToConsole(
"DecodeNIString - Failed to decode string(length %d)", hexstring_length);
wide_str.AssignLiteral("Failed to decode string.");
return wide_str;
}
int byteArrayLen = (hexstring_length / 2);
if (aEncodingType == GPS_ENC_SUPL_UTF8 || aEncodingType == GPS_ENC_SUPL_GSM_DEFAULT) {
byteArrayLen += 1; //for string terminator
} else if (aEncodingType == GPS_ENC_SUPL_UCS2) {
byteArrayLen += 2; //for string terminator
// Determine endianess of the data
if (hexstring_length >= 4) {
const char bom_be[] = "FEFF"; // Byte-Order Mark in big endian
const char bom_le[] = "FFFE"; // Byte-Order Mark in little endian
if (memcmp(bom_be, aMessage, sizeof(bom_be) - 1) == 0) {
little_endian = false; // big endian detected
starthexIndex += 4; // skip the Byte-Order Mark
} else if (memcmp(bom_le, aMessage, sizeof(bom_le) - 1) == 0) {
starthexIndex += 4; // skip the Byte-Order Mark
}
}
}
UniquePtr<char[]> byteArray = MakeUnique<char[]>(byteArrayLen);
// In this loop we have to read 4 bytes at a time so as to take care of
// endianess while converting hex string to byte array.
// If the hexstring_length is not divisble by 4, we still take care of
// remaining bytes outside the for loop.
for (; starthexIndex <= hexstring_length - 4; starthexIndex += 4, byteIndex += 2) {
char hexArray1[3] = {aMessage[starthexIndex], aMessage[starthexIndex + 1], '\0'};
char hexArray2[3] = {aMessage[starthexIndex + 2], aMessage[starthexIndex + 3], '\0'};
if (little_endian) {
byteArray[byteIndex] = (char) strtol(hexArray1, nullptr, 16);
byteArray[byteIndex + 1] = (char) strtol(hexArray2, nullptr, 16);
} else {
byteArray[byteIndex] = (char) strtol(hexArray2, nullptr, 16);
byteArray[byteIndex + 1] = (char) strtol(hexArray1, nullptr, 16);
}
}
if (starthexIndex <= hexstring_length - 2) {
char hexArray[3] = {aMessage[starthexIndex], aMessage[starthexIndex + 1],'\0'};
byteArray[byteIndex] = (char) strtol(hexArray, nullptr, 16);
}
// decode byte string by different encodings (support UTF8, USC2, GSM_DEFAULT)
if (aEncodingType == GPS_ENC_SUPL_UTF8) {
nsCString str(byteArray.get());
wide_str.Append(NS_ConvertUTF8toUTF16(str));
} else if (aEncodingType == GPS_ENC_SUPL_UCS2) {
wide_str.Append((const char16_t*)byteArray.get());
} else if (aEncodingType == GPS_ENC_SUPL_GSM_DEFAULT) {
if (!DecodeGsmDefaultToString(byteArray, wide_str)) {
nsContentUtils::LogMessageToConsole("DecodeNIString - Decode Failed!");
wide_str.AssignLiteral("Failed to decode string");
}
} else {
nsContentUtils::LogMessageToConsole("DecodeNIString - UnknownEncoding.");
wide_str.AssignLiteral("Failed to decode string");
}
// The |NI message| is composed by "ni_id, text, requestor_id".
// We need to distinguish the comma in |NI message| from the comma
// in text or requestor_id. After discussion, we use escape to do so.
wide_str.ReplaceSubstring(MOZ_UTF16("\\"), MOZ_UTF16("\\\\"));
wide_str.ReplaceSubstring(MOZ_UTF16(","), MOZ_UTF16("\\,"));
return wide_str;
}
} // namespace mozilla

View File

@ -7,7 +7,23 @@
#ifndef GEOLOCATIONUTIL_H
#define GEOLOCATIONUTIL_H
#include "nsPrintfCString.h"
#include "mozilla/UniquePtr.h"
namespace mozilla {
/**
* NI data encoding scheme
*/
typedef int GpsNiEncodingType;
#define GPS_ENC_NONE 0
#define GPS_ENC_SUPL_GSM_DEFAULT 1
#define GPS_ENC_SUPL_UTF8 2
#define GPS_ENC_SUPL_UCS2 3
#define GPS_ENC_UNKNOWN -1
double CalculateDeltaInMeter(double aLat, double aLon, double aLastLat, double aLastLon);
bool DecodeGsmDefaultToString(UniquePtr<char[]> &aByteArray, nsString &aWideStr);
nsString DecodeNIString(const char* aMessage, GpsNiEncodingType aEncodingType);
}
#endif

View File

@ -352,7 +352,7 @@ GonkGPSGeolocationProvider::SetNiResponse(int id, int response)
}
bool
GonkGPSGeolocationProvider::SendChromeEvent(int id, GpsNiNotifyFlags flags)
GonkGPSGeolocationProvider::SendChromeEvent(int id, GpsNiNotifyFlags flags, nsString& message)
{
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
@ -361,16 +361,38 @@ GonkGPSGeolocationProvider::SendChromeEvent(int id, GpsNiNotifyFlags flags)
RefPtr<GonkGPSGeolocationProvider> provider = GonkGPSGeolocationProvider::GetSingleton();
provider->SetNiResponse(id, GPS_NI_RESPONSE_NORESP);
}
nsContentUtils::LogMessageToConsole(
"geo: SendChromeEvent Failed(id:%d, flags:%x)", id, flags);
return false;
}
nsCString str = nsPrintfCString("%d", id);
if (flags == GPS_NI_NEED_NOTIFY) {
obs->NotifyObservers(nullptr, SUPL_NI_NOTIFY, NS_ConvertUTF8toUTF16(str).get());
} else if (flags == GPS_NI_NEED_VERIFY) {
obs->NotifyObservers(nullptr, SUPL_NI_VERIFY, NS_ConvertUTF8toUTF16(str).get());
} else if (flags == GPS_NI_NEED_TIMEOUT) {
obs->NotifyObservers(nullptr, SUPL_NI_VERIFY_TIMEOUT, NS_ConvertUTF8toUTF16(str).get());
if (flags == GPS_NI_NEED_TIMEOUT) {
obs->NotifyObservers(nullptr, SUPL_NI_VERIFY_TIMEOUT, message.get());
return true;
}
bool needNotify = (flags & GPS_NI_NEED_NOTIFY) != 0;
bool needVerify = (flags & GPS_NI_NEED_VERIFY) != 0;
bool privacyOverride = (flags & GPS_NI_PRIVACY_OVERRIDE) != 0;
// Verify behavior in gaia include notify and dialog in current
// implementation.
// So the logic here is different from Android
if (needNotify && needVerify) {
obs->NotifyObservers(nullptr, SUPL_NI_VERIFY, message.get());
} else if (needNotify) {
obs->NotifyObservers(nullptr, SUPL_NI_NOTIFY, message.get());
} else {
RefPtr<GonkGPSGeolocationProvider> provider =
GonkGPSGeolocationProvider::GetSingleton();
provider->SetNiResponse(id, GPS_NI_RESPONSE_ACCEPT);
}
if (privacyOverride) {
RefPtr<GonkGPSGeolocationProvider> provider =
GonkGPSGeolocationProvider::GetSingleton();
provider->SetNiResponse(id, GPS_NI_RESPONSE_ACCEPT);
}
return true;
}
@ -390,23 +412,46 @@ GonkGPSGeolocationProvider::GPSNiNotifyCallback(GpsNiNotification *notification)
if (gDebug_isLoggingEnabled) {
nsContentUtils::LogMessageToConsole(
"GPSNiNotifyCallback id:%d, flag:%x, timeout:%d, default response:%d\n",
"geo: GPSNiNotifyCallback id:%d, flag:%x, timeout:%d, default response:%d\n",
id, flags, mNotification->timeout, mNotification->default_response);
}
RefPtr<GonkGPSGeolocationProvider> provider =
GonkGPSGeolocationProvider::GetSingleton();
if(!provider->SendChromeEvent(id, flags)) {
nsContentUtils::LogMessageToConsole(
"SendChromeEvent Failed(id:%d, flags:%x)", id, flags);
return NS_OK;
// The message is composed by "notification_id, notification_text,
// notification_requestor_id"
// If the encoding of text and requestor_id is not supported, the message
// would be "id, ,".
nsString message;
message.AppendInt(id);
message.AppendLiteral(",");
GpsNiEncodingType encodingType = mNotification->text_encoding;
if (encodingType == GPS_ENC_SUPL_UTF8 ||
encodingType == GPS_ENC_SUPL_UCS2 ||
encodingType == GPS_ENC_SUPL_GSM_DEFAULT) {
message.Append(DecodeNIString(mNotification->text, encodingType));
} else {
nsContentUtils::LogMessageToConsole(
"geo: GPSNiNotifyCallback text in unsupport decodeing format(%d)\n", encodingType);
}
message.AppendLiteral(",");
encodingType = mNotification->requestor_id_encoding;
if (encodingType == GPS_ENC_SUPL_UTF8 ||
encodingType == GPS_ENC_SUPL_UCS2 ||
encodingType == GPS_ENC_SUPL_GSM_DEFAULT) {
message.Append(DecodeNIString(mNotification->requestor_id, encodingType));
} else {
nsContentUtils::LogMessageToConsole(
"geo: GPSNiNotifyCallback requestor_id in unsupport decodeing format(%d)\n", encodingType);
}
provider->SendChromeEvent(id, flags, message);
class TimeoutResponseEvent : public Task {
public:
TimeoutResponseEvent(int id, int defaultResp)
: mId(id), mDefaultResp(defaultResp)
TimeoutResponseEvent(int id, int defaultResp, nsString msg)
: mId(id), mDefaultResp(defaultResp), mMsg(msg)
{}
void Run() {
for (uint32_t idx = 0; idx < repliedSuplNiReqIds.Length() ; idx++) {
@ -417,9 +462,9 @@ GonkGPSGeolocationProvider::GPSNiNotifyCallback(GpsNiNotification *notification)
}
RefPtr<GonkGPSGeolocationProvider> provider =
GonkGPSGeolocationProvider::GetSingleton();
if (!provider->SendChromeEvent(mId, GPS_NI_NEED_TIMEOUT)) {
if (!provider->SendChromeEvent(mId, GPS_NI_NEED_TIMEOUT, mMsg)) {
nsContentUtils::LogMessageToConsole(
"SendChromeEvent Failed(id:%d, flags:%x", mId, GPS_NI_NEED_TIMEOUT);
"geo: SendChromeEvent Failed(id:%d, flags:%x", mId, GPS_NI_NEED_TIMEOUT);
}
provider->SetNiResponse(mId, mDefaultResp);
return;
@ -427,14 +472,16 @@ GonkGPSGeolocationProvider::GPSNiNotifyCallback(GpsNiNotification *notification)
private:
int mId;
int mDefaultResp;
nsString mMsg;
};
if (flags == GPS_NI_NEED_VERIFY) {
MessageLoop::current()->PostDelayedTask(FROM_HERE,
new TimeoutResponseEvent(id, mNotification->default_response),
mNotification->timeout*1000);
}
return NS_OK;
bool needVerify = (flags & GPS_NI_NEED_VERIFY) != 0;
if (needVerify) {
MessageLoop::current()->PostDelayedTask(FROM_HERE,
new TimeoutResponseEvent(id, mNotification->default_response, message),
mNotification->timeout*1000);
}
return NS_OK;
}
private:
GpsNiNotification *mNotification;

View File

@ -84,7 +84,7 @@ private:
void InjectLocation(double latitude, double longitude, float accuracy);
void RequestSettingValue(const char* aKey);
void SetNiResponse(int id, int response);
bool SendChromeEvent(int id, GpsNiNotifyFlags flags);
bool SendChromeEvent(int id, GpsNiNotifyFlags flags, nsString& message);
#ifdef MOZ_B2G_RIL
void UpdateRadioInterface();
bool IsValidRilServiceId(uint32_t aServiceId);

View File

@ -0,0 +1,106 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "gtest/gtest.h"
#include "GeolocationUtil.h"
#include "mozilla/UniquePtr.h"
#include "nsTArray.h"
using namespace mozilla;
using mozilla::UniquePtr;
TEST(GSMDefaultDecode, DecodeAlphabet) {
#define NUM_DECODE_GSM_TEST 4
const char* testArray[NUM_DECODE_GSM_TEST] = {
"\x41\xE1\x90\x58\x34\x1E\x91",
"\x49\xE5\x92\xD9\x74\x3E\xA1",
"\x51\xE9\x94\x5A\xB5\x5E\xB1",
"\x41\xE1\x90\x58\x34\x1E\x91"
"\x49\xE5\x92\xD9\x74\x3E\xA1"
"\x51\xE9\x94\x5A\xB5\x5E\xB1\x59\x2D"};
nsTArray<nsString> result;
result.AppendElement(NS_LITERAL_STRING("ABCDEFGH"));
result.AppendElement(NS_LITERAL_STRING("IJKLMNOP"));
result.AppendElement(NS_LITERAL_STRING("QRSTUVWX"));
result.AppendElement(NS_LITERAL_STRING("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
for (int i = 0; i < NUM_DECODE_GSM_TEST ; i++) {
nsString decodedStr;
UniquePtr<char[]> byteArray = MakeUnique<char[]>(strlen(testArray[i])+1);
strcpy(byteArray.get(), testArray[i]);
ASSERT_TRUE(DecodeGsmDefaultToString(byteArray, decodedStr));
ASSERT_TRUE(decodedStr == result[i]) <<
"[test " << i << "]" << "The expected result is " <<
NS_ConvertUTF16toUTF8(result[i]).get() << "(" <<
"current:" << NS_ConvertUTF16toUTF8(decodedStr).get() << ")!";
}
}
TEST(GSMDefaultDecode, DecodeNullptr) {
UniquePtr<char[]> byteArray;
nsString decodedStr;
ASSERT_FALSE(DecodeGsmDefaultToString(byteArray, decodedStr));
}
TEST(DecodeNIString, DecodeUTF8) {
#define NUM_DECODE_UTF8_TEST 4
const char* testArray[NUM_DECODE_UTF8_TEST] = {
"4142434445464748",
"494A4B4C4D4E4F50",
"5152535455565758",
"4142434445464748494A4B4C4D4E4F505152535455565758595A"};
nsTArray<nsString> result;
result.AppendElement(NS_LITERAL_STRING("ABCDEFGH"));
result.AppendElement(NS_LITERAL_STRING("IJKLMNOP"));
result.AppendElement(NS_LITERAL_STRING("QRSTUVWX"));
result.AppendElement(NS_LITERAL_STRING("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
for (int i = 0; i < NUM_DECODE_UTF8_TEST ; i++) {
nsString decodedStr;
decodedStr = DecodeNIString(testArray[i], GPS_ENC_SUPL_UTF8);
ASSERT_TRUE(decodedStr == result[i]) <<
"[test " << i << "]" << "The expected result is " <<
NS_ConvertUTF16toUTF8(result[i]).get() << "(" <<
"current:" << NS_ConvertUTF16toUTF8(decodedStr).get() << ")!";
}
}
TEST(DecodeNIString, DecodeUSC2) {
#define NUM_DECODE_USC2_TEST 4
const char* testArray[NUM_DECODE_USC2_TEST] = {
"FEFF00410042004300440045004600470048",
"FEFF0049004A004B004C004D004E004F0050",
"FEFF00510052005300540055005600570058",
"FEFF004100420043004400450046004700480049004A004B004C004D004E00"
"4F0050005100520053005400550056005700580059005A"};
nsTArray<nsString> result;
result.AppendElement(NS_LITERAL_STRING("ABCDEFGH"));
result.AppendElement(NS_LITERAL_STRING("IJKLMNOP"));
result.AppendElement(NS_LITERAL_STRING("QRSTUVWX"));
result.AppendElement(NS_LITERAL_STRING("ABCDEFGHIJKLMNOPQRSTUVWXYZ"));
for (int i = 0; i < NUM_DECODE_USC2_TEST ; i++) {
nsString decodedStr;
decodedStr = DecodeNIString(testArray[i], GPS_ENC_SUPL_UCS2);
ASSERT_TRUE(decodedStr == result[i]) <<
"[test " << i << "]" << "The expected result is " <<
NS_ConvertUTF16toUTF8(result[i]).get() << "(" <<
"current:" << NS_ConvertUTF16toUTF8(decodedStr).get() << ")!";
}
}
TEST(DecodeNIString, DecodeUnknown) {
const char* testStr= {"4F0050005100520053005400550056005700580059005A"};
nsString decodedStr;
#define GPS_ENC_SUPL_UNKNOWN 0xFF
decodedStr = DecodeNIString(testStr, GPS_ENC_SUPL_UNKNOWN);
ASSERT_TRUE(decodedStr.EqualsLiteral("Failed to decode string")) <<
"The expected result is Failed to decode string(current:" <<
NS_ConvertUTF16toUTF8(decodedStr).get() << ")!";
}

View File

@ -0,0 +1,19 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
UNIFIED_SOURCES += [
'TestDecodeNIString.cpp',
]
SOURCES += [
'/dom/system/gonk/GeolocationUtil.cpp',
]
LOCAL_INCLUDES += [
'/dom/system/gonk',
]
FINAL_LIBRARY = 'xul-gtest'

View File

@ -64,5 +64,9 @@ DEFINES['DLL_SUFFIX'] = '"%s"' % CONFIG['DLL_SUFFIX']
MOCHITEST_CHROME_MANIFESTS += ['tests/chrome.ini']
MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
TEST_DIRS += [
'gonk/tests/gtest'
]
if CONFIG['GNU_CXX']:
CXXFLAGS += ['-Wshadow']