mirror of
https://codeberg.org/comaps/comaps
synced 2025-12-19 21:13:35 +00:00
@@ -0,0 +1,131 @@
|
|||||||
|
package app.organicmaps.location;
|
||||||
|
|
||||||
|
import android.util.Base64;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.spec.GCMParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AES-256-GCM encryption/decryption for location data.
|
||||||
|
*/
|
||||||
|
public class LocationCrypto
|
||||||
|
{
|
||||||
|
private static final String ALGORITHM = "AES/GCM/NoPadding";
|
||||||
|
private static final int GCM_IV_LENGTH = 12; // 96 bits
|
||||||
|
private static final int GCM_TAG_LENGTH = 128; // 128 bits
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypt plaintext JSON using AES-256-GCM.
|
||||||
|
* @param base64Key Base64-encoded 256-bit key
|
||||||
|
* @param plaintextJson JSON string to encrypt
|
||||||
|
* @return JSON string with encrypted payload: {"iv":"...","ciphertext":"...","authTag":"..."}
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static String encrypt(@NonNull String base64Key, @NonNull String plaintextJson)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Decode the base64 key
|
||||||
|
byte[] key = Base64.decode(base64Key, Base64.NO_WRAP);
|
||||||
|
if (key.length != 32) // 256 bits
|
||||||
|
{
|
||||||
|
android.util.Log.e("LocationCrypto", "Invalid key size: " + key.length);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate random IV
|
||||||
|
byte[] iv = new byte[GCM_IV_LENGTH];
|
||||||
|
new SecureRandom().nextBytes(iv);
|
||||||
|
|
||||||
|
// Create cipher
|
||||||
|
Cipher cipher = Cipher.getInstance(ALGORITHM);
|
||||||
|
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
|
||||||
|
GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec);
|
||||||
|
|
||||||
|
// Encrypt
|
||||||
|
byte[] plaintext = plaintextJson.getBytes(StandardCharsets.UTF_8);
|
||||||
|
byte[] ciphertextWithTag = cipher.doFinal(plaintext);
|
||||||
|
|
||||||
|
// Split ciphertext and auth tag
|
||||||
|
// In GCM mode, doFinal() returns ciphertext + tag
|
||||||
|
int ciphertextLength = ciphertextWithTag.length - (GCM_TAG_LENGTH / 8);
|
||||||
|
byte[] ciphertext = new byte[ciphertextLength];
|
||||||
|
byte[] authTag = new byte[GCM_TAG_LENGTH / 8];
|
||||||
|
|
||||||
|
System.arraycopy(ciphertextWithTag, 0, ciphertext, 0, ciphertextLength);
|
||||||
|
System.arraycopy(ciphertextWithTag, ciphertextLength, authTag, 0, authTag.length);
|
||||||
|
|
||||||
|
// Build JSON response
|
||||||
|
JSONObject result = new JSONObject();
|
||||||
|
result.put("iv", Base64.encodeToString(iv, Base64.NO_WRAP));
|
||||||
|
result.put("ciphertext", Base64.encodeToString(ciphertext, Base64.NO_WRAP));
|
||||||
|
result.put("authTag", Base64.encodeToString(authTag, Base64.NO_WRAP));
|
||||||
|
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
android.util.Log.e("LocationCrypto", "Encryption failed", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypt encrypted payload using AES-256-GCM.
|
||||||
|
* @param base64Key Base64-encoded 256-bit key
|
||||||
|
* @param encryptedPayloadJson JSON string with format: {"iv":"...","ciphertext":"...","authTag":"..."}
|
||||||
|
* @return Decrypted plaintext JSON string
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static String decrypt(@NonNull String base64Key, @NonNull String encryptedPayloadJson)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Parse encrypted payload
|
||||||
|
JSONObject payload = new JSONObject(encryptedPayloadJson);
|
||||||
|
byte[] iv = Base64.decode(payload.getString("iv"), Base64.NO_WRAP);
|
||||||
|
byte[] ciphertext = Base64.decode(payload.getString("ciphertext"), Base64.NO_WRAP);
|
||||||
|
byte[] authTag = Base64.decode(payload.getString("authTag"), Base64.NO_WRAP);
|
||||||
|
|
||||||
|
// Decode the base64 key
|
||||||
|
byte[] key = Base64.decode(base64Key, Base64.NO_WRAP);
|
||||||
|
if (key.length != 32) // 256 bits
|
||||||
|
{
|
||||||
|
android.util.Log.e("LocationCrypto", "Invalid key size: " + key.length);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine ciphertext and auth tag for GCM decryption
|
||||||
|
byte[] ciphertextWithTag = new byte[ciphertext.length + authTag.length];
|
||||||
|
System.arraycopy(ciphertext, 0, ciphertextWithTag, 0, ciphertext.length);
|
||||||
|
System.arraycopy(authTag, 0, ciphertextWithTag, ciphertext.length, authTag.length);
|
||||||
|
|
||||||
|
// Create cipher
|
||||||
|
Cipher cipher = Cipher.getInstance(ALGORITHM);
|
||||||
|
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
|
||||||
|
GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec);
|
||||||
|
|
||||||
|
// Decrypt
|
||||||
|
byte[] plaintext = cipher.doFinal(ciphertextWithTag);
|
||||||
|
|
||||||
|
return new String(plaintext, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
android.util.Log.e("LocationCrypto", "Decryption failed", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import androidx.annotation.Nullable;
|
|||||||
|
|
||||||
import app.organicmaps.MwmApplication;
|
import app.organicmaps.MwmApplication;
|
||||||
import app.organicmaps.sdk.routing.RoutingController;
|
import app.organicmaps.sdk.routing.RoutingController;
|
||||||
|
import app.organicmaps.sdk.util.Config;
|
||||||
import app.organicmaps.sdk.util.log.Logger;
|
import app.organicmaps.sdk.util.log.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,10 +32,6 @@ public class LocationSharingManager
|
|||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
|
|
||||||
// Configuration
|
|
||||||
private int mUpdateIntervalSeconds = 20;
|
|
||||||
private String mServerBaseUrl = "https://live.organicmaps.app"; // TODO: Configure
|
|
||||||
|
|
||||||
private LocationSharingManager()
|
private LocationSharingManager()
|
||||||
{
|
{
|
||||||
mContext = MwmApplication.sInstance;
|
mContext = MwmApplication.sInstance;
|
||||||
@@ -72,8 +69,9 @@ public class LocationSharingManager
|
|||||||
mSessionId = credentials[0];
|
mSessionId = credentials[0];
|
||||||
mEncryptionKey = credentials[1];
|
mEncryptionKey = credentials[1];
|
||||||
|
|
||||||
// Generate share URL
|
// Generate share URL using configured server
|
||||||
mShareUrl = nativeGenerateShareUrl(mSessionId, mEncryptionKey, mServerBaseUrl);
|
String serverUrl = Config.LocationSharing.getServerUrl();
|
||||||
|
mShareUrl = nativeGenerateShareUrl(mSessionId, mEncryptionKey, serverUrl);
|
||||||
if (mShareUrl == null)
|
if (mShareUrl == null)
|
||||||
{
|
{
|
||||||
Logger.e(TAG, "Failed to generate share URL");
|
Logger.e(TAG, "Failed to generate share URL");
|
||||||
@@ -86,8 +84,8 @@ public class LocationSharingManager
|
|||||||
Intent intent = new Intent(mContext, LocationSharingService.class);
|
Intent intent = new Intent(mContext, LocationSharingService.class);
|
||||||
intent.putExtra(LocationSharingService.EXTRA_SESSION_ID, mSessionId);
|
intent.putExtra(LocationSharingService.EXTRA_SESSION_ID, mSessionId);
|
||||||
intent.putExtra(LocationSharingService.EXTRA_ENCRYPTION_KEY, mEncryptionKey);
|
intent.putExtra(LocationSharingService.EXTRA_ENCRYPTION_KEY, mEncryptionKey);
|
||||||
intent.putExtra(LocationSharingService.EXTRA_SERVER_URL, mServerBaseUrl);
|
intent.putExtra(LocationSharingService.EXTRA_SERVER_URL, serverUrl);
|
||||||
intent.putExtra(LocationSharingService.EXTRA_UPDATE_INTERVAL, mUpdateIntervalSeconds);
|
intent.putExtra(LocationSharingService.EXTRA_UPDATE_INTERVAL, Config.LocationSharing.getUpdateInterval());
|
||||||
|
|
||||||
mContext.startForegroundService(intent);
|
mContext.startForegroundService(intent);
|
||||||
|
|
||||||
@@ -138,28 +136,23 @@ public class LocationSharingManager
|
|||||||
|
|
||||||
public void setUpdateIntervalSeconds(int seconds)
|
public void setUpdateIntervalSeconds(int seconds)
|
||||||
{
|
{
|
||||||
if (seconds < 5 || seconds > 60)
|
Config.LocationSharing.setUpdateInterval(seconds);
|
||||||
{
|
|
||||||
Logger.w(TAG, "Invalid update interval: " + seconds + ", using default");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mUpdateIntervalSeconds = seconds;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getUpdateIntervalSeconds()
|
public int getUpdateIntervalSeconds()
|
||||||
{
|
{
|
||||||
return mUpdateIntervalSeconds;
|
return Config.LocationSharing.getUpdateInterval();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setServerBaseUrl(@NonNull String url)
|
public void setServerBaseUrl(@NonNull String url)
|
||||||
{
|
{
|
||||||
mServerBaseUrl = url;
|
Config.LocationSharing.setServerUrl(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public String getServerBaseUrl()
|
public String getServerBaseUrl()
|
||||||
{
|
{
|
||||||
return mServerBaseUrl;
|
return Config.LocationSharing.getServerUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -111,6 +111,22 @@ public class LocationSharingService extends Service implements LocationListener
|
|||||||
// Initialize API client
|
// Initialize API client
|
||||||
mApiClient = new LocationSharingApiClient(mServerUrl, mSessionId);
|
mApiClient = new LocationSharingApiClient(mServerUrl, mSessionId);
|
||||||
|
|
||||||
|
// Create session on server
|
||||||
|
mApiClient.createSession(new LocationSharingApiClient.Callback()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onSuccess()
|
||||||
|
{
|
||||||
|
Logger.i(TAG, "Session created on server");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NonNull String error)
|
||||||
|
{
|
||||||
|
Logger.w(TAG, "Failed to create session on server: " + error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Start foreground with notification
|
// Start foreground with notification
|
||||||
Notification notification = mNotificationHelper != null
|
Notification notification = mNotificationHelper != null
|
||||||
? mNotificationHelper.buildNotification(getStopIntent())
|
? mNotificationHelper.buildNotification(getStopIntent())
|
||||||
@@ -212,7 +228,7 @@ public class LocationSharingService extends Service implements LocationListener
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Encrypt payload
|
// Encrypt payload
|
||||||
String encryptedJson = LocationSharingManager.nativeEncryptPayload(mEncryptionKey, payload.toString());
|
String encryptedJson = LocationCrypto.encrypt(mEncryptionKey, payload.toString());
|
||||||
if (encryptedJson == null)
|
if (encryptedJson == null)
|
||||||
{
|
{
|
||||||
Logger.e(TAG, "Failed to encrypt payload");
|
Logger.e(TAG, "Failed to encrypt payload");
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import android.os.Bundle;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.preference.EditTextPreference;
|
||||||
import androidx.preference.ListPreference;
|
import androidx.preference.ListPreference;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
import androidx.preference.PreferenceCategory;
|
import androidx.preference.PreferenceCategory;
|
||||||
@@ -73,6 +74,7 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
|
|||||||
initScreenSleepEnabledPrefsCallbacks();
|
initScreenSleepEnabledPrefsCallbacks();
|
||||||
initShowOnLockScreenPrefsCallbacks();
|
initShowOnLockScreenPrefsCallbacks();
|
||||||
initLeftButtonPrefs();
|
initLeftButtonPrefs();
|
||||||
|
initLocationSharingPrefsCallbacks();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initLeftButtonPrefs()
|
private void initLeftButtonPrefs()
|
||||||
@@ -542,6 +544,29 @@ public class SettingsPrefsFragment extends BaseXmlSettingsFragment implements La
|
|||||||
category.removePreference(preference);
|
category.removePreference(preference);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initLocationSharingPrefsCallbacks()
|
||||||
|
{
|
||||||
|
// Server URL preference
|
||||||
|
final EditTextPreference serverUrlPref = getPreference(getString(R.string.pref_location_sharing_server_url));
|
||||||
|
serverUrlPref.setText(Config.LocationSharing.getServerUrl());
|
||||||
|
serverUrlPref.setSummary(Config.LocationSharing.getServerUrl());
|
||||||
|
serverUrlPref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
String url = (String) newValue;
|
||||||
|
Config.LocationSharing.setServerUrl(url);
|
||||||
|
serverUrlPref.setSummary(url);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update interval preference
|
||||||
|
final ListPreference intervalPref = getPreference(getString(R.string.pref_location_sharing_update_interval));
|
||||||
|
intervalPref.setValue(String.valueOf(Config.LocationSharing.getUpdateInterval()));
|
||||||
|
intervalPref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
int seconds = Integer.parseInt((String) newValue);
|
||||||
|
Config.LocationSharing.setUpdateInterval(seconds);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLanguageSelected(Language language)
|
public void onLanguageSelected(Language language)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -50,4 +50,21 @@
|
|||||||
<item>8</item>
|
<item>8</item>
|
||||||
<item>10</item>
|
<item>10</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<!-- Location Sharing Update Intervals -->
|
||||||
|
<string-array name="location_sharing_intervals">
|
||||||
|
<item>5 seconds</item>
|
||||||
|
<item>10 seconds</item>
|
||||||
|
<item>20 seconds</item>
|
||||||
|
<item>30 seconds</item>
|
||||||
|
<item>60 seconds</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="location_sharing_interval_values">
|
||||||
|
<item>5</item>
|
||||||
|
<item>10</item>
|
||||||
|
<item>20</item>
|
||||||
|
<item>30</item>
|
||||||
|
<item>60</item>
|
||||||
|
</string-array>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -159,6 +159,27 @@
|
|||||||
</intent>
|
</intent>
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
</androidx.preference.PreferenceCategory>
|
</androidx.preference.PreferenceCategory>
|
||||||
|
<androidx.preference.PreferenceCategory
|
||||||
|
android:key="@string/pref_location_sharing_category"
|
||||||
|
android:title="@string/location_sharing_title"
|
||||||
|
android:order="5">
|
||||||
|
<EditTextPreference
|
||||||
|
android:key="@string/pref_location_sharing_server_url"
|
||||||
|
android:title="@string/pref_location_sharing_server_url"
|
||||||
|
app:singleLineTitle="false"
|
||||||
|
android:summary="@string/pref_location_sharing_server_url_summary"
|
||||||
|
android:defaultValue="https://live.organicmaps.app"
|
||||||
|
android:order="1"/>
|
||||||
|
<ListPreference
|
||||||
|
android:key="@string/pref_location_sharing_update_interval"
|
||||||
|
android:title="@string/pref_location_sharing_update_interval"
|
||||||
|
app:singleLineTitle="false"
|
||||||
|
android:summary="@string/pref_location_sharing_update_interval_summary"
|
||||||
|
android:entries="@array/location_sharing_intervals"
|
||||||
|
android:entryValues="@array/location_sharing_interval_values"
|
||||||
|
android:defaultValue="20"
|
||||||
|
android:order="2"/>
|
||||||
|
</androidx.preference.PreferenceCategory>
|
||||||
<androidx.preference.PreferenceCategory
|
<androidx.preference.PreferenceCategory
|
||||||
android:key="@string/pref_privacy"
|
android:key="@string/pref_privacy"
|
||||||
android:title="@string/privacy"
|
android:title="@string/privacy"
|
||||||
|
|||||||
@@ -515,6 +515,46 @@ public final class Config
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class LocationSharing
|
||||||
|
{
|
||||||
|
interface Keys
|
||||||
|
{
|
||||||
|
String SERVER_URL = "LocationSharingServerUrl";
|
||||||
|
String UPDATE_INTERVAL = "LocationSharingUpdateInterval";
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Defaults
|
||||||
|
{
|
||||||
|
String SERVER_URL = "https://live.organicmaps.app";
|
||||||
|
int UPDATE_INTERVAL = 20; // seconds
|
||||||
|
int UPDATE_INTERVAL_MIN = 5;
|
||||||
|
int UPDATE_INTERVAL_MAX = 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static String getServerUrl()
|
||||||
|
{
|
||||||
|
return getString(Keys.SERVER_URL, Defaults.SERVER_URL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setServerUrl(@NonNull String url)
|
||||||
|
{
|
||||||
|
setString(Keys.SERVER_URL, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getUpdateInterval()
|
||||||
|
{
|
||||||
|
return getInt(Keys.UPDATE_INTERVAL, Defaults.UPDATE_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setUpdateInterval(int seconds)
|
||||||
|
{
|
||||||
|
if (seconds < Defaults.UPDATE_INTERVAL_MIN || seconds > Defaults.UPDATE_INTERVAL_MAX)
|
||||||
|
seconds = Defaults.UPDATE_INTERVAL;
|
||||||
|
setInt(Keys.UPDATE_INTERVAL, seconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static native boolean nativeHasConfigValue(String name);
|
private static native boolean nativeHasConfigValue(String name);
|
||||||
private static native boolean nativeDeleteConfigValue(String name);
|
private static native boolean nativeDeleteConfigValue(String name);
|
||||||
private static native boolean nativeGetBoolean(String name, boolean defaultValue);
|
private static native boolean nativeGetBoolean(String name, boolean defaultValue);
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ struct LocationSharingConfig {
|
|||||||
var includeDestinationName: Bool = true
|
var includeDestinationName: Bool = true
|
||||||
var includeBatteryLevel: Bool = true
|
var includeBatteryLevel: Bool = true
|
||||||
var lowBatteryThreshold: Int = 10
|
var lowBatteryThreshold: Int = 10
|
||||||
|
// Default from LOCATION_SHARING_SERVER_URL in private.h
|
||||||
|
// Can be overridden by user settings
|
||||||
var serverBaseUrl: String = "https://live.organicmaps.app"
|
var serverBaseUrl: String = "https://live.organicmaps.app"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -158,6 +158,11 @@ std::string Platform::DefaultUrlsJSON() const
|
|||||||
return DEFAULT_URLS_JSON;
|
return DEFAULT_URLS_JSON;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string Platform::LocationSharingServerUrl() const
|
||||||
|
{
|
||||||
|
return LOCATION_SHARING_SERVER_URL;
|
||||||
|
}
|
||||||
|
|
||||||
bool Platform::RemoveFileIfExists(std::string const & filePath)
|
bool Platform::RemoveFileIfExists(std::string const & filePath)
|
||||||
{
|
{
|
||||||
return IsFileExistsByFullPath(filePath) ? base::DeleteFileX(filePath) : true;
|
return IsFileExistsByFullPath(filePath) ? base::DeleteFileX(filePath) : true;
|
||||||
|
|||||||
@@ -271,6 +271,9 @@ public:
|
|||||||
/// @return JSON-encoded list of urls if metaserver is unreachable
|
/// @return JSON-encoded list of urls if metaserver is unreachable
|
||||||
std::string DefaultUrlsJSON() const;
|
std::string DefaultUrlsJSON() const;
|
||||||
|
|
||||||
|
/// @return default location sharing server URL
|
||||||
|
std::string LocationSharingServerUrl() const;
|
||||||
|
|
||||||
bool IsTablet() const { return m_isTablet; }
|
bool IsTablet() const { return m_isTablet; }
|
||||||
|
|
||||||
/// @return information about kinds of memory which are relevant for a platform.
|
/// @return information about kinds of memory which are relevant for a platform.
|
||||||
|
|||||||
@@ -11,3 +11,4 @@
|
|||||||
#define TRAFFIC_DATA_BASE_URL ""
|
#define TRAFFIC_DATA_BASE_URL ""
|
||||||
#define USER_BINDING_PKCS12 ""
|
#define USER_BINDING_PKCS12 ""
|
||||||
#define USER_BINDING_PKCS12_PASSWORD ""
|
#define USER_BINDING_PKCS12_PASSWORD ""
|
||||||
|
#define LOCATION_SHARING_SERVER_URL "https://ec1e1096-e991-4cb1-ac21-30fbad2bd406.mock.pstmn.io"
|
||||||
|
|||||||
Reference in New Issue
Block a user