summaryrefslogtreecommitdiffstats
path: root/dom/system/gonk/mozstumbler
diff options
context:
space:
mode:
Diffstat (limited to 'dom/system/gonk/mozstumbler')
-rw-r--r--dom/system/gonk/mozstumbler/MozStumbler.cpp426
-rw-r--r--dom/system/gonk/mozstumbler/MozStumbler.h47
-rw-r--r--dom/system/gonk/mozstumbler/StumblerLogging.cpp13
-rw-r--r--dom/system/gonk/mozstumbler/StumblerLogging.h18
-rw-r--r--dom/system/gonk/mozstumbler/UploadStumbleRunnable.cpp151
-rw-r--r--dom/system/gonk/mozstumbler/UploadStumbleRunnable.h46
-rw-r--r--dom/system/gonk/mozstumbler/WriteStumbleOnThread.cpp321
-rw-r--r--dom/system/gonk/mozstumbler/WriteStumbleOnThread.h91
8 files changed, 1113 insertions, 0 deletions
diff --git a/dom/system/gonk/mozstumbler/MozStumbler.cpp b/dom/system/gonk/mozstumbler/MozStumbler.cpp
new file mode 100644
index 000000000..61e09e705
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/MozStumbler.cpp
@@ -0,0 +1,426 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "MozStumbler.h"
+#include "nsDataHashtable.h"
+#include "nsGeoPosition.h"
+#include "nsNetCID.h"
+#include "nsPrintfCString.h"
+#include "StumblerLogging.h"
+#include "WriteStumbleOnThread.h"
+#include "../GeolocationUtil.h"
+
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIMobileConnectionInfo.h"
+#include "nsIMobileConnectionService.h"
+#include "nsIMobileCellInfo.h"
+#include "nsIMobileNetworkInfo.h"
+#include "nsINetworkInterface.h"
+#include "nsIRadioInterfaceLayer.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+
+NS_IMPL_ISUPPORTS(StumblerInfo, nsICellInfoListCallback, nsIWifiScanResultsReady)
+
+class RequestCellInfoEvent : public Runnable {
+public:
+ RequestCellInfoEvent(StumblerInfo *callback)
+ : mRequestCallback(callback)
+ {}
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ // Get Cell Info
+ nsCOMPtr<nsIMobileConnectionService> service =
+ do_GetService(NS_MOBILE_CONNECTION_SERVICE_CONTRACTID);
+
+ if (!service) {
+ STUMBLER_ERR("Stumbler-can not get nsIMobileConnectionService \n");
+ return NS_OK;
+ }
+ nsCOMPtr<nsIMobileConnection> connection;
+ uint32_t numberOfRilServices = 1, cellInfoNum = 0;
+
+ service->GetNumItems(&numberOfRilServices);
+ for (uint32_t rilNum = 0; rilNum < numberOfRilServices; rilNum++) {
+ service->GetItemByServiceId(rilNum /* Client Id */, getter_AddRefs(connection));
+ if (!connection) {
+ STUMBLER_ERR("Stumbler-can not get nsIMobileConnection by ServiceId %d \n", rilNum);
+ } else {
+ cellInfoNum++;
+ connection->GetCellInfoList(mRequestCallback);
+ }
+ }
+ mRequestCallback->SetCellInfoResponsesExpected(cellInfoNum);
+
+ // Get Wifi AP Info
+ nsCOMPtr<nsIInterfaceRequestor> ir = do_GetService("@mozilla.org/telephony/system-worker-manager;1");
+ nsCOMPtr<nsIWifi> wifi = do_GetInterface(ir);
+ if (!wifi) {
+ mRequestCallback->SetWifiInfoResponseReceived();
+ STUMBLER_ERR("Stumbler-can not get nsIWifi interface\n");
+ return NS_OK;
+ }
+ wifi->GetWifiScanResults(mRequestCallback);
+ return NS_OK;
+ }
+private:
+ RefPtr<StumblerInfo> mRequestCallback;
+};
+
+void
+MozStumble(nsGeoPosition* position)
+{
+ if (WriteStumbleOnThread::IsFileWaitingForUpload()) {
+ nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ MOZ_ASSERT(target);
+ // Knowing that file is waiting to upload, and no collection will take place,
+ // just trigger the thread with an empty string.
+ nsCOMPtr<nsIRunnable> event = new WriteStumbleOnThread(EmptyCString());
+ target->Dispatch(event, NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ nsCOMPtr<nsIDOMGeoPositionCoords> coords;
+ position->GetCoords(getter_AddRefs(coords));
+ if (!coords) {
+ return;
+ }
+
+ double latitude, longitude;
+ coords->GetLatitude(&latitude);
+ coords->GetLongitude(&longitude);
+
+ const double kMinChangeInMeters = 30;
+ static int64_t lastTime_ms = 0;
+ static double sLastLat = 0;
+ static double sLastLon = 0;
+ double delta = -1.0;
+ int64_t timediff = (PR_Now() / PR_USEC_PER_MSEC) - lastTime_ms;
+
+ if (0 != sLastLon || 0 != sLastLat) {
+ delta = CalculateDeltaInMeter(latitude, longitude, sLastLat, sLastLon);
+ }
+ STUMBLER_DBG("Stumbler-Location. [%f , %f] time_diff:%lld, delta : %f\n",
+ longitude, latitude, timediff, delta);
+
+ // Consecutive GPS locations must be 30 meters and 3 seconds apart
+ if (lastTime_ms == 0 || ((timediff >= STUMBLE_INTERVAL_MS) && (delta > kMinChangeInMeters))){
+ lastTime_ms = (PR_Now() / PR_USEC_PER_MSEC);
+ sLastLat = latitude;
+ sLastLon = longitude;
+ RefPtr<StumblerInfo> requestCallback = new StumblerInfo(position);
+ RefPtr<RequestCellInfoEvent> runnable = new RequestCellInfoEvent(requestCallback);
+ NS_DispatchToMainThread(runnable);
+ } else {
+ STUMBLER_DBG("Stumbler-GPS locations less than 30 meters and 3 seconds. Ignore!\n");
+ }
+}
+
+void
+StumblerInfo::SetWifiInfoResponseReceived()
+{
+ mIsWifiInfoResponseReceived = true;
+
+ if (mIsWifiInfoResponseReceived && mCellInfoResponsesReceived == mCellInfoResponsesExpected) {
+ STUMBLER_DBG("Call DumpStumblerInfo from SetWifiInfoResponseReceived\n");
+ DumpStumblerInfo();
+ }
+}
+
+void
+StumblerInfo::SetCellInfoResponsesExpected(uint8_t count)
+{
+ mCellInfoResponsesExpected = count;
+ STUMBLER_DBG("SetCellInfoNum (%d)\n", count);
+
+ if (mIsWifiInfoResponseReceived && mCellInfoResponsesReceived == mCellInfoResponsesExpected) {
+ STUMBLER_DBG("Call DumpStumblerInfo from SetCellInfoResponsesExpected\n");
+ DumpStumblerInfo();
+ }
+}
+
+
+#define TEXT_LAT NS_LITERAL_CSTRING("latitude")
+#define TEXT_LON NS_LITERAL_CSTRING("longitude")
+#define TEXT_ACC NS_LITERAL_CSTRING("accuracy")
+#define TEXT_ALT NS_LITERAL_CSTRING("altitude")
+#define TEXT_ALTACC NS_LITERAL_CSTRING("altitudeAccuracy")
+#define TEXT_HEAD NS_LITERAL_CSTRING("heading")
+#define TEXT_SPD NS_LITERAL_CSTRING("speed")
+
+nsresult
+StumblerInfo::LocationInfoToString(nsACString& aLocDesc)
+{
+ nsCOMPtr<nsIDOMGeoPositionCoords> coords;
+ mPosition->GetCoords(getter_AddRefs(coords));
+ if (!coords) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsDataHashtable<nsCStringHashKey, double> info;
+
+ double val;
+ coords->GetLatitude(&val);
+ info.Put(TEXT_LAT, val);
+ coords->GetLongitude(&val);
+ info.Put(TEXT_LON, val);
+ coords->GetAccuracy(&val);
+ info.Put(TEXT_ACC, val);
+ coords->GetAltitude(&val);
+ info.Put(TEXT_ALT, val);
+ coords->GetAltitudeAccuracy(&val);
+ info.Put(TEXT_ALTACC, val);
+ coords->GetHeading(&val);
+ info.Put(TEXT_HEAD, val);
+ coords->GetSpeed(&val);
+ info.Put(TEXT_SPD, val);
+
+ for (auto it = info.Iter(); !it.Done(); it.Next()) {
+ const nsACString& key = it.Key();
+ val = it.UserData();
+ if (!IsNaN(val)) {
+ aLocDesc += nsPrintfCString("\"%s\":%f,", key.BeginReading(), val);
+ }
+ }
+
+ aLocDesc += nsPrintfCString("\"timestamp\":%lld,", PR_Now() / PR_USEC_PER_MSEC).get();
+ return NS_OK;
+}
+
+#define TEXT_RADIOTYPE NS_LITERAL_CSTRING("radioType")
+#define TEXT_MCC NS_LITERAL_CSTRING("mobileCountryCode")
+#define TEXT_MNC NS_LITERAL_CSTRING("mobileNetworkCode")
+#define TEXT_LAC NS_LITERAL_CSTRING("locationAreaCode")
+#define TEXT_CID NS_LITERAL_CSTRING("cellId")
+#define TEXT_PSC NS_LITERAL_CSTRING("psc")
+#define TEXT_STRENGTH_ASU NS_LITERAL_CSTRING("asu")
+#define TEXT_STRENGTH_DBM NS_LITERAL_CSTRING("signalStrength")
+#define TEXT_REGISTERED NS_LITERAL_CSTRING("serving")
+#define TEXT_TIMEING_ADVANCE NS_LITERAL_CSTRING("timingAdvance")
+
+template <class T> void
+ExtractCommonNonCDMACellInfoItems(nsCOMPtr<T>& cell, nsDataHashtable<nsCStringHashKey, int32_t>& info)
+{
+ int32_t mcc, mnc, cid, sig;
+
+ cell->GetMcc(&mcc);
+ cell->GetMnc(&mnc);
+ cell->GetCid(&cid);
+ cell->GetSignalStrength(&sig);
+
+ info.Put(TEXT_MCC, mcc);
+ info.Put(TEXT_MNC, mnc);
+ info.Put(TEXT_CID, cid);
+ info.Put(TEXT_STRENGTH_ASU, sig);
+}
+
+void
+StumblerInfo::CellNetworkInfoToString(nsACString& aCellDesc)
+{
+ aCellDesc += "\"cellTowers\": [";
+
+ for (uint32_t idx = 0; idx < mCellInfo.Length() ; idx++) {
+ const char* radioType = 0;
+ int32_t type;
+ mCellInfo[idx]->GetType(&type);
+ bool registered;
+ mCellInfo[idx]->GetRegistered(&registered);
+ if (idx) {
+ aCellDesc += ",{";
+ } else {
+ aCellDesc += "{";
+ }
+
+ STUMBLER_DBG("type=%d\n", type);
+
+ nsDataHashtable<nsCStringHashKey, int32_t> info;
+ info.Put(TEXT_REGISTERED, registered);
+
+ if(type == nsICellInfo::CELL_INFO_TYPE_GSM) {
+ radioType = "gsm";
+ nsCOMPtr<nsIGsmCellInfo> gsmCellInfo = do_QueryInterface(mCellInfo[idx]);
+ ExtractCommonNonCDMACellInfoItems(gsmCellInfo, info);
+ int32_t lac;
+ gsmCellInfo->GetLac(&lac);
+ info.Put(TEXT_LAC, lac);
+ } else if (type == nsICellInfo::CELL_INFO_TYPE_WCDMA) {
+ radioType = "wcdma";
+ nsCOMPtr<nsIWcdmaCellInfo> wcdmaCellInfo = do_QueryInterface(mCellInfo[idx]);
+ ExtractCommonNonCDMACellInfoItems(wcdmaCellInfo, info);
+ int32_t lac, psc;
+ wcdmaCellInfo->GetLac(&lac);
+ wcdmaCellInfo->GetPsc(&psc);
+ info.Put(TEXT_LAC, lac);
+ info.Put(TEXT_PSC, psc);
+ } else if (type == nsICellInfo::CELL_INFO_TYPE_CDMA) {
+ radioType = "cdma";
+ nsCOMPtr<nsICdmaCellInfo> cdmaCellInfo = do_QueryInterface(mCellInfo[idx]);
+ int32_t mnc, lac, cid, sig;
+ cdmaCellInfo->GetSystemId(&mnc);
+ cdmaCellInfo->GetNetworkId(&lac);
+ cdmaCellInfo->GetBaseStationId(&cid);
+ info.Put(TEXT_MNC, mnc);
+ info.Put(TEXT_LAC, lac);
+ info.Put(TEXT_CID, cid);
+
+ cdmaCellInfo->GetEvdoDbm(&sig);
+ if (sig < 0 || sig == nsICellInfo::UNKNOWN_VALUE) {
+ cdmaCellInfo->GetCdmaDbm(&sig);
+ }
+ if (sig > -1 && sig != nsICellInfo::UNKNOWN_VALUE) {
+ sig *= -1;
+ info.Put(TEXT_STRENGTH_DBM, sig);
+ }
+ } else if (type == nsICellInfo::CELL_INFO_TYPE_LTE) {
+ radioType = "lte";
+ nsCOMPtr<nsILteCellInfo> lteCellInfo = do_QueryInterface(mCellInfo[idx]);
+ ExtractCommonNonCDMACellInfoItems(lteCellInfo, info);
+ int32_t lac, timingAdvance, pcid, rsrp;
+ lteCellInfo->GetTac(&lac);
+ lteCellInfo->GetTimingAdvance(&timingAdvance);
+ lteCellInfo->GetPcid(&pcid);
+ lteCellInfo->GetRsrp(&rsrp);
+ info.Put(TEXT_LAC, lac);
+ info.Put(TEXT_TIMEING_ADVANCE, timingAdvance);
+ info.Put(TEXT_PSC, pcid);
+ if (rsrp != nsICellInfo::UNKNOWN_VALUE) {
+ info.Put(TEXT_STRENGTH_DBM, rsrp * -1);
+ }
+ }
+
+ aCellDesc += nsPrintfCString("\"%s\":\"%s\"", TEXT_RADIOTYPE.get(), radioType);
+ for (auto it = info.Iter(); !it.Done(); it.Next()) {
+ const nsACString& key = it.Key();
+ int32_t value = it.UserData();
+ if (value != nsICellInfo::UNKNOWN_VALUE) {
+ aCellDesc += nsPrintfCString(",\"%s\":%d", key.BeginReading(), value);
+ }
+ }
+
+ aCellDesc += "}";
+ }
+ aCellDesc += "]";
+}
+
+void
+StumblerInfo::DumpStumblerInfo()
+{
+ if (!mIsWifiInfoResponseReceived || mCellInfoResponsesReceived != mCellInfoResponsesExpected) {
+ STUMBLER_DBG("CellInfoReceived=%d (Expected=%d), WifiInfoResponseReceived=%d\n",
+ mCellInfoResponsesReceived, mCellInfoResponsesExpected, mIsWifiInfoResponseReceived);
+ return;
+ }
+ mIsWifiInfoResponseReceived = false;
+ mCellInfoResponsesReceived = 0;
+
+ nsAutoCString desc;
+ nsresult rv = LocationInfoToString(desc);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("LocationInfoToString failed, skip this dump");
+ return;
+ }
+
+ CellNetworkInfoToString(desc);
+ desc += mWifiDesc;
+
+ STUMBLER_DBG("dispatch write event to thread\n");
+ nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ MOZ_ASSERT(target);
+
+ nsCOMPtr<nsIRunnable> event = new WriteStumbleOnThread(desc);
+ target->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+NS_IMETHODIMP
+StumblerInfo::NotifyGetCellInfoList(uint32_t count, nsICellInfo** aCellInfos)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ STUMBLER_DBG("There are %d cellinfo in the result\n", count);
+
+ for (uint32_t i = 0; i < count; i++) {
+ mCellInfo.AppendElement(aCellInfos[i]);
+ }
+ mCellInfoResponsesReceived++;
+ DumpStumblerInfo();
+ return NS_OK;
+}
+
+NS_IMETHODIMP StumblerInfo::NotifyGetCellInfoListFailed(const nsAString& error)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ mCellInfoResponsesReceived++;
+ STUMBLER_ERR("NotifyGetCellInfoListFailedm CellInfoReadyNum=%d, mCellInfoResponsesExpected=%d, mIsWifiInfoResponseReceived=%d",
+ mCellInfoResponsesReceived, mCellInfoResponsesExpected, mIsWifiInfoResponseReceived);
+ DumpStumblerInfo();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StumblerInfo::Onready(uint32_t count, nsIWifiScanResult** results)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ STUMBLER_DBG("There are %d wifiAPinfo in the result\n",count);
+
+ mWifiDesc += ",\"wifiAccessPoints\": [";
+ bool firstItem = true;
+ for (uint32_t i = 0 ; i < count ; i++) {
+ nsString ssid;
+ results[i]->GetSsid(ssid);
+ if (ssid.IsEmpty()) {
+ STUMBLER_DBG("no ssid, skip this AP\n");
+ continue;
+ }
+
+ if (ssid.Length() >= 6) {
+ if (StringEndsWith(ssid, NS_LITERAL_STRING("_nomap"))) {
+ STUMBLER_DBG("end with _nomap. skip this AP(ssid :%s)\n", ssid.get());
+ continue;
+ }
+ }
+
+ if (firstItem) {
+ mWifiDesc += "{";
+ firstItem = false;
+ } else {
+ mWifiDesc += ",{";
+ }
+
+ // mac address
+ nsString bssid;
+ results[i]->GetBssid(bssid);
+ // 00:00:00:00:00:00 --> 000000000000
+ bssid.StripChars(":");
+ mWifiDesc += "\"macAddress\":\"";
+ mWifiDesc += NS_ConvertUTF16toUTF8(bssid);
+
+ uint32_t signal;
+ results[i]->GetSignalStrength(&signal);
+ mWifiDesc += "\",\"signalStrength\":";
+ mWifiDesc.AppendInt(signal);
+
+ mWifiDesc += "}";
+ }
+ mWifiDesc += "]";
+
+ mIsWifiInfoResponseReceived = true;
+ DumpStumblerInfo();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+StumblerInfo::Onfailure()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ STUMBLER_ERR("GetWifiScanResults Onfailure\n");
+ mIsWifiInfoResponseReceived = true;
+ DumpStumblerInfo();
+ return NS_OK;
+}
+
diff --git a/dom/system/gonk/mozstumbler/MozStumbler.h b/dom/system/gonk/mozstumbler/MozStumbler.h
new file mode 100644
index 000000000..41ee4e5e1
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/MozStumbler.h
@@ -0,0 +1,47 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_system_mozstumbler_h__
+#define mozilla_system_mozstumbler_h__
+
+#include "nsIDOMEventTarget.h"
+#include "nsICellInfo.h"
+#include "nsIWifi.h"
+
+#define STUMBLE_INTERVAL_MS 3000
+
+class nsGeoPosition;
+
+void MozStumble(nsGeoPosition* position);
+
+class StumblerInfo final : public nsICellInfoListCallback,
+ public nsIWifiScanResultsReady
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICELLINFOLISTCALLBACK
+ NS_DECL_NSIWIFISCANRESULTSREADY
+
+ explicit StumblerInfo(nsGeoPosition* position)
+ : mPosition(position), mCellInfoResponsesExpected(0), mCellInfoResponsesReceived(0), mIsWifiInfoResponseReceived(0)
+ {}
+ void SetWifiInfoResponseReceived();
+ void SetCellInfoResponsesExpected(uint8_t count);
+
+private:
+ ~StumblerInfo() {}
+ void DumpStumblerInfo();
+ nsresult LocationInfoToString(nsACString& aLocDesc);
+ void CellNetworkInfoToString(nsACString& aCellDesc);
+ nsTArray<RefPtr<nsICellInfo>> mCellInfo;
+ nsCString mWifiDesc;
+ RefPtr<nsGeoPosition> mPosition;
+ int mCellInfoResponsesExpected;
+ int mCellInfoResponsesReceived;
+ bool mIsWifiInfoResponseReceived;
+};
+#endif // mozilla_system_mozstumbler_h__
+
diff --git a/dom/system/gonk/mozstumbler/StumblerLogging.cpp b/dom/system/gonk/mozstumbler/StumblerLogging.cpp
new file mode 100644
index 000000000..acf23b3b1
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/StumblerLogging.cpp
@@ -0,0 +1,13 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "StumblerLogging.h"
+
+mozilla::LogModule* GetLog()
+{
+ static mozilla::LazyLogModule log("mozstumbler");
+ return log;
+}
diff --git a/dom/system/gonk/mozstumbler/StumblerLogging.h b/dom/system/gonk/mozstumbler/StumblerLogging.h
new file mode 100644
index 000000000..038f44f8f
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/StumblerLogging.h
@@ -0,0 +1,18 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef STUMBLERLOGGING_H
+#define STUMBLERLOGGING_H
+
+#include "mozilla/Logging.h"
+
+mozilla::LogModule* GetLog();
+
+#define STUMBLER_DBG(arg, ...) MOZ_LOG(GetLog(), mozilla::LogLevel::Debug, ("STUMBLER - %s: " arg, __func__, ##__VA_ARGS__))
+#define STUMBLER_LOG(arg, ...) MOZ_LOG(GetLog(), mozilla::LogLevel::Info, ("STUMBLER - %s: " arg, __func__, ##__VA_ARGS__))
+#define STUMBLER_ERR(arg, ...) MOZ_LOG(GetLog(), mozilla::LogLevel::Error, ("STUMBLER -%s: " arg, __func__, ##__VA_ARGS__))
+
+#endif
diff --git a/dom/system/gonk/mozstumbler/UploadStumbleRunnable.cpp b/dom/system/gonk/mozstumbler/UploadStumbleRunnable.cpp
new file mode 100644
index 000000000..d97aa9712
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/UploadStumbleRunnable.cpp
@@ -0,0 +1,151 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "UploadStumbleRunnable.h"
+#include "StumblerLogging.h"
+#include "mozilla/dom/Event.h"
+#include "nsIInputStream.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIURLFormatter.h"
+#include "nsIXMLHttpRequest.h"
+#include "nsNetUtil.h"
+#include "nsVariant.h"
+
+UploadStumbleRunnable::UploadStumbleRunnable(nsIInputStream* aUploadData)
+: mUploadInputStream(aUploadData)
+{
+}
+
+NS_IMETHODIMP
+UploadStumbleRunnable::Run()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsresult rv = Upload();
+ if (NS_FAILED(rv)) {
+ WriteStumbleOnThread::UploadEnded(false);
+ }
+ return NS_OK;
+}
+
+nsresult
+UploadStumbleRunnable::Upload()
+{
+ nsresult rv;
+ RefPtr<nsVariant> variant = new nsVariant();
+
+ rv = variant->SetAsISupports(mUploadInputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIXMLHttpRequest> xhr = do_CreateInstance(NS_XMLHTTPREQUEST_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIScriptSecurityManager> secman =
+ do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> systemPrincipal;
+ rv = secman->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = xhr->Init(systemPrincipal, nullptr, nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURLFormatter> formatter =
+ do_CreateInstance("@mozilla.org/toolkit/URLFormatterService;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString url;
+ rv = formatter->FormatURLPref(NS_LITERAL_STRING("geo.stumbler.url"), url);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = xhr->Open(NS_LITERAL_CSTRING("POST"), NS_ConvertUTF16toUTF8(url), false, EmptyString(), EmptyString());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ xhr->SetRequestHeader(NS_LITERAL_CSTRING("Content-Type"), NS_LITERAL_CSTRING("gzip"));
+ xhr->SetMozBackgroundRequest(true);
+ // 60s timeout
+ xhr->SetTimeout(60 * 1000);
+
+ nsCOMPtr<EventTarget> target(do_QueryInterface(xhr));
+ RefPtr<nsIDOMEventListener> listener = new UploadEventListener(xhr);
+
+ const char* const sEventStrings[] = {
+ // nsIXMLHttpRequestEventTarget event types
+ "abort",
+ "error",
+ "load",
+ "timeout"
+ };
+
+ for (uint32_t index = 0; index < MOZ_ARRAY_LENGTH(sEventStrings); index++) {
+ nsAutoString eventType = NS_ConvertASCIItoUTF16(sEventStrings[index]);
+ rv = target->AddEventListener(eventType, listener, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = xhr->Send(variant);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(UploadEventListener, nsIDOMEventListener)
+
+UploadEventListener::UploadEventListener(nsIXMLHttpRequest* aXHR)
+: mXHR(aXHR)
+{
+}
+
+NS_IMETHODIMP
+UploadEventListener::HandleEvent(nsIDOMEvent* aEvent)
+{
+ nsString type;
+ if (NS_FAILED(aEvent->GetType(type))) {
+ STUMBLER_ERR("Failed to get event type");
+ WriteStumbleOnThread::UploadEnded(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (type.EqualsLiteral("load")) {
+ STUMBLER_DBG("Got load Event\n");
+ } else if (type.EqualsLiteral("error") && mXHR) {
+ STUMBLER_ERR("Upload Error");
+ } else {
+ STUMBLER_DBG("Receive %s Event", NS_ConvertUTF16toUTF8(type).get());
+ }
+
+ uint32_t statusCode = 0;
+ bool doDelete = false;
+ if (!mXHR) {
+ return NS_OK;
+ }
+ nsresult rv = mXHR->GetStatus(&statusCode);
+ if (NS_SUCCEEDED(rv)) {
+ STUMBLER_DBG("statuscode %d \n", statusCode);
+ }
+
+ if (200 == statusCode || 400 == statusCode) {
+ doDelete = true;
+ }
+
+ WriteStumbleOnThread::UploadEnded(doDelete);
+ nsCOMPtr<EventTarget> target(do_QueryInterface(mXHR));
+
+ const char* const sEventStrings[] = {
+ // nsIXMLHttpRequestEventTarget event types
+ "abort",
+ "error",
+ "load",
+ "timeout"
+ };
+
+ for (uint32_t index = 0; index < MOZ_ARRAY_LENGTH(sEventStrings); index++) {
+ nsAutoString eventType = NS_ConvertASCIItoUTF16(sEventStrings[index]);
+ rv = target->RemoveEventListener(eventType, this, false);
+ }
+
+ mXHR = nullptr;
+ return NS_OK;
+}
diff --git a/dom/system/gonk/mozstumbler/UploadStumbleRunnable.h b/dom/system/gonk/mozstumbler/UploadStumbleRunnable.h
new file mode 100644
index 000000000..462665a86
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/UploadStumbleRunnable.h
@@ -0,0 +1,46 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+
+#ifndef UPLOADSTUMBLERUNNABLE_H
+#define UPLOADSTUMBLERUNNABLE_H
+
+#include "nsIDOMEventListener.h"
+
+class nsIXMLHttpRequest;
+class nsIInputStream;
+
+/*
+ This runnable is managed by WriteStumbleOnThread only, see that class
+ for how this is scheduled.
+ */
+class UploadStumbleRunnable final : public Runnable
+{
+public:
+ explicit UploadStumbleRunnable(nsIInputStream* aUploadInputStream);
+
+ NS_IMETHOD Run() override;
+private:
+ virtual ~UploadStumbleRunnable() {}
+ nsCOMPtr<nsIInputStream> mUploadInputStream;
+ nsresult Upload();
+};
+
+
+class UploadEventListener : public nsIDOMEventListener
+{
+public:
+ UploadEventListener(nsIXMLHttpRequest* aXHR);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+
+protected:
+ virtual ~UploadEventListener() {}
+ nsCOMPtr<nsIXMLHttpRequest> mXHR;
+};
+
+#endif
diff --git a/dom/system/gonk/mozstumbler/WriteStumbleOnThread.cpp b/dom/system/gonk/mozstumbler/WriteStumbleOnThread.cpp
new file mode 100644
index 000000000..e58e771c4
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/WriteStumbleOnThread.cpp
@@ -0,0 +1,321 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "WriteStumbleOnThread.h"
+#include "StumblerLogging.h"
+#include "UploadStumbleRunnable.h"
+#include "nsDumpUtils.h"
+#include "nsGZFileWriter.h"
+#include "nsIFileStreams.h"
+#include "nsIInputStream.h"
+#include "nsPrintfCString.h"
+
+#define MAXFILESIZE_KB (15 * 1024)
+#define ONEDAY_IN_MSEC (24 * 60 * 60 * 1000)
+#define MAX_UPLOAD_ATTEMPTS 20
+
+mozilla::Atomic<bool> WriteStumbleOnThread::sIsFileWaitingForUpload(false);
+mozilla::Atomic<bool> WriteStumbleOnThread::sIsAlreadyRunning(false);
+WriteStumbleOnThread::UploadFreqGuard WriteStumbleOnThread::sUploadFreqGuard = {0};
+
+#define FILENAME_INPROGRESS NS_LITERAL_CSTRING("stumbles.json.gz")
+#define FILENAME_COMPLETED NS_LITERAL_CSTRING("stumbles.done.json.gz")
+#define OUTPUT_DIR NS_LITERAL_CSTRING("mozstumbler")
+
+class DeleteRunnable : public Runnable
+{
+ public:
+ DeleteRunnable() {}
+
+ NS_IMETHOD
+ Run() override
+ {
+ nsCOMPtr<nsIFile> tmpFile;
+ nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED,
+ getter_AddRefs(tmpFile),
+ OUTPUT_DIR,
+ nsDumpUtils::CREATE);
+ if (NS_SUCCEEDED(rv)) {
+ tmpFile->Remove(true);
+ }
+ // critically, this sets this flag to false so writing can happen again
+ WriteStumbleOnThread::sIsAlreadyRunning = false;
+ WriteStumbleOnThread::sIsFileWaitingForUpload = false;
+ return NS_OK;
+ }
+
+ private:
+ ~DeleteRunnable() {}
+};
+
+bool
+WriteStumbleOnThread::IsFileWaitingForUpload()
+{
+ return sIsFileWaitingForUpload;
+}
+
+void
+WriteStumbleOnThread::UploadEnded(bool deleteUploadFile)
+{
+ if (!deleteUploadFile) {
+ sIsAlreadyRunning = false;
+ return;
+ }
+
+ nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ MOZ_ASSERT(target);
+ nsCOMPtr<nsIRunnable> event = new DeleteRunnable();
+ target->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+void
+WriteStumbleOnThread::WriteJSON(Partition aPart)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsCOMPtr<nsIFile> tmpFile;
+ nsresult rv;
+ rv = nsDumpUtils::OpenTempFile(FILENAME_INPROGRESS, getter_AddRefs(tmpFile),
+ OUTPUT_DIR, nsDumpUtils::CREATE);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("Open a file for stumble failed");
+ return;
+ }
+
+ RefPtr<nsGZFileWriter> gzWriter = new nsGZFileWriter(nsGZFileWriter::Append);
+ rv = gzWriter->Init(tmpFile);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("gzWriter init failed");
+ return;
+ }
+
+ /*
+ The json format is like below.
+ {items:[
+ {item},
+ {item},
+ {item}
+ ]}
+ */
+
+ // Need to add "]}" after the last item
+ if (aPart == Partition::End) {
+ rv = gzWriter->Write("]}");
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("gzWriter Write failed");
+ }
+
+ rv = gzWriter->Finish();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("ostream finish failed");
+ }
+
+ nsCOMPtr<nsIFile> targetFile;
+ nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED, getter_AddRefs(targetFile),
+ OUTPUT_DIR, nsDumpUtils::CREATE);
+ nsAutoString targetFilename;
+ rv = targetFile->GetLeafName(targetFilename);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("Get Filename failed");
+ return;
+ }
+ rv = targetFile->Remove(true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("Remove File failed");
+ return;
+ }
+ // Rename tmpfile
+ rv = tmpFile->MoveTo(/* directory */ nullptr, targetFilename);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("Rename File failed");
+ return;
+ }
+ return;
+ }
+
+ // Need to add "{items:[" before the first item
+ if (aPart == Partition::Begining) {
+ rv = gzWriter->Write("{\"items\":[{");
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("ostream write begining failed");
+ }
+ } else if (aPart == Partition::Middle) {
+ rv = gzWriter->Write(",{");
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("ostream write middle failed");
+ }
+ }
+ rv = gzWriter->Write(mDesc.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("ostream write mDesc failed");
+ }
+ // one item is ended with '}' (e.g. {item})
+ rv = gzWriter->Write("}");
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("ostream write end failed");
+ }
+
+ rv = gzWriter->Finish();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("ostream finish failed");
+ }
+
+ // check if it is the end of this file
+ int64_t fileSize = 0;
+ rv = tmpFile->GetFileSize(&fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("GetFileSize failed");
+ return;
+ }
+ if (fileSize >= MAXFILESIZE_KB) {
+ WriteJSON(Partition::End);
+ return;
+ }
+}
+
+WriteStumbleOnThread::Partition
+WriteStumbleOnThread::GetWritePosition()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsCOMPtr<nsIFile> tmpFile;
+ nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_INPROGRESS, getter_AddRefs(tmpFile),
+ OUTPUT_DIR, nsDumpUtils::CREATE);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("Open a file for stumble failed");
+ return Partition::Unknown;
+ }
+
+ int64_t fileSize = 0;
+ rv = tmpFile->GetFileSize(&fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("GetFileSize failed");
+ return Partition::Unknown;
+ }
+
+ if (fileSize == 0) {
+ return Partition::Begining;
+ } else if (fileSize >= MAXFILESIZE_KB) {
+ return Partition::End;
+ } else {
+ return Partition::Middle;
+ }
+}
+
+NS_IMETHODIMP
+WriteStumbleOnThread::Run()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ bool b = sIsAlreadyRunning.exchange(true);
+ if (b) {
+ return NS_OK;
+ }
+
+ UploadFileStatus status = GetUploadFileStatus();
+
+ if (UploadFileStatus::NoFile != status) {
+ if (UploadFileStatus::ExistsAndReadyToUpload == status) {
+ sIsFileWaitingForUpload = true;
+ Upload();
+ return NS_OK;
+ }
+ } else {
+ Partition partition = GetWritePosition();
+ if (partition == Partition::Unknown) {
+ STUMBLER_ERR("GetWritePosition failed, skip once");
+ } else {
+ WriteJSON(partition);
+ }
+ }
+
+ sIsFileWaitingForUpload = false;
+ sIsAlreadyRunning = false;
+ return NS_OK;
+}
+
+
+/*
+ If the upload file exists, then check if it is one day old.
+ • if it is a day old -> ExistsAndReadyToUpload
+ • if it is less than the current day old -> Exists
+ • otherwise -> NoFile
+
+ The Exists case means that the upload and the stumbling is rate limited
+ per-day to the size of the one file.
+ */
+WriteStumbleOnThread::UploadFileStatus
+WriteStumbleOnThread::GetUploadFileStatus()
+{
+ nsCOMPtr<nsIFile> tmpFile;
+ nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED, getter_AddRefs(tmpFile),
+ OUTPUT_DIR, nsDumpUtils::CREATE);
+ int64_t fileSize;
+ rv = tmpFile->GetFileSize(&fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("GetFileSize failed");
+ return UploadFileStatus::NoFile;
+ }
+ if (fileSize <= 0) {
+ tmpFile->Remove(true);
+ return UploadFileStatus::NoFile;
+ }
+
+ PRTime lastModifiedTime;
+ tmpFile->GetLastModifiedTime(&lastModifiedTime);
+ if ((PR_Now() / PR_USEC_PER_MSEC) - lastModifiedTime >= ONEDAY_IN_MSEC) {
+ return UploadFileStatus::ExistsAndReadyToUpload;
+ }
+ return UploadFileStatus::Exists;
+}
+
+void
+WriteStumbleOnThread::Upload()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ time_t seconds = time(0);
+ int day = seconds / (60 * 60 * 24);
+
+ if (sUploadFreqGuard.daySinceEpoch < day) {
+ sUploadFreqGuard.daySinceEpoch = day;
+ sUploadFreqGuard.attempts = 0;
+ }
+
+ sUploadFreqGuard.attempts++;
+ if (sUploadFreqGuard.attempts > MAX_UPLOAD_ATTEMPTS) {
+ STUMBLER_ERR("Too many upload attempts today");
+ sIsAlreadyRunning = false;
+ return;
+ }
+
+ nsCOMPtr<nsIFile> tmpFile;
+ nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED, getter_AddRefs(tmpFile),
+ OUTPUT_DIR, nsDumpUtils::CREATE);
+ int64_t fileSize;
+ rv = tmpFile->GetFileSize(&fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("GetFileSize failed");
+ sIsAlreadyRunning = false;
+ return;
+ }
+
+ if (fileSize <= 0) {
+ sIsAlreadyRunning = false;
+ return;
+ }
+
+ // prepare json into nsIInputStream
+ nsCOMPtr<nsIInputStream> inStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inStream), tmpFile);
+ if (NS_FAILED(rv)) {
+ sIsAlreadyRunning = false;
+ return;
+ }
+
+ RefPtr<nsIRunnable> uploader = new UploadStumbleRunnable(inStream);
+ NS_DispatchToMainThread(uploader);
+}
diff --git a/dom/system/gonk/mozstumbler/WriteStumbleOnThread.h b/dom/system/gonk/mozstumbler/WriteStumbleOnThread.h
new file mode 100644
index 000000000..104cf9bdd
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/WriteStumbleOnThread.h
@@ -0,0 +1,91 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef WriteStumbleOnThread_H
+#define WriteStumbleOnThread_H
+
+#include "mozilla/Atomics.h"
+
+class DeleteRunnable;
+
+/*
+ This class is the entry point to stumbling, in that it
+ receives the location+cell+wifi string and writes it
+ to disk, or instead, it calls UploadStumbleRunnable
+ to upload the data.
+
+ Writes will happen until the file is a max size, then stop.
+ Uploads will happen only when the file is one day old.
+ The purpose of these decisions is to have very simple rate-limiting
+ on the writes, as well as the uploads.
+
+ There is only one file active; it is either being used for writing,
+ or for uploading. If the file is ready for uploading, no further
+ writes will take place until this file has been uploaded.
+ This can mean writing might not take place for days until the uploaded
+ file is processed. This is correct by-design.
+
+ A notable limitation is that the upload is triggered by a location event,
+ this is used as an arbitrary and simple trigger. In future, there are
+ better events that can be used, such as detecting network activity.
+
+ This thread is guarded so that only one instance is active (see the
+ mozilla::Atomics used for this).
+ */
+class WriteStumbleOnThread : public mozilla::Runnable
+{
+public:
+ explicit WriteStumbleOnThread(const nsCString& aDesc)
+ : mDesc(aDesc)
+ {}
+
+ NS_IMETHOD Run() override;
+
+ static void UploadEnded(bool deleteUploadFile);
+
+ // Used externally to determine if cell+wifi scans should happen
+ // (returns false for that case).
+ static bool IsFileWaitingForUpload();
+
+private:
+ friend class DeleteRunnable;
+
+ enum class Partition {
+ Begining,
+ Middle,
+ End,
+ Unknown
+ };
+
+ enum class UploadFileStatus {
+ NoFile, Exists, ExistsAndReadyToUpload
+ };
+
+ ~WriteStumbleOnThread() {}
+
+ Partition GetWritePosition();
+ UploadFileStatus GetUploadFileStatus();
+ void WriteJSON(Partition aPart);
+ void Upload();
+
+ nsCString mDesc;
+
+ // Only run one instance of this
+ static mozilla::Atomic<bool> sIsAlreadyRunning;
+
+ static mozilla::Atomic<bool> sIsFileWaitingForUpload;
+
+ // Limit the upload attempts per day. If the device is rebooted
+ // this resets the allowed attempts, which is acceptable.
+ struct UploadFreqGuard {
+ int attempts;
+ int daySinceEpoch;
+ };
+ static UploadFreqGuard sUploadFreqGuard;
+
+};
+
+#endif