Aligning mixpanel implementation with Java & Cocoa (#513)
Aligning mixpanel implementation with Java & Cocoa
This commit is contained in:
parent
c026c198ea
commit
b06ee441bd
|
@ -0,0 +1,244 @@
|
|||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.net.URL;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Set;
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Scanner;
|
||||
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
|
||||
// Submits build information to Realm when assembling the app
|
||||
//
|
||||
// To be clear: this does *not* run when your app is in production or on
|
||||
// your end-user's devices; it will only run when you build your app from source.
|
||||
//
|
||||
// Why are we doing this? Because it helps us build a better product for you.
|
||||
// None of the data personally identifies you, your employer or your app, but it
|
||||
// *will* help us understand what Realm version you use, what host OS you use,
|
||||
// etc. Having this info will help with prioritizing our time, adding new
|
||||
// features and deprecating old features. Collecting an anonymized bundle &
|
||||
// anonymized MAC is the only way for us to count actual usage of the other
|
||||
// metrics accurately. If we don't have a way to deduplicate the info reported,
|
||||
// it will be useless, as a single developer building their app on Windows ten
|
||||
// times would report 10 times more than a single developer that only builds
|
||||
// once from Mac OS X, making the data all but useless. No one likes sharing
|
||||
// data unless it's necessary, we get it, and we've debated adding this for a
|
||||
// long long time. Since Realm is a free product without an email signup, we
|
||||
// feel this is a necessary step so we can collect relevant data to build a
|
||||
// better product for you.
|
||||
//
|
||||
// Currently the following information is reported:
|
||||
// - What version of Realm is being used
|
||||
// - What OS you are running on
|
||||
// - An anonymized MAC address and bundle ID to aggregate the other information on.
|
||||
|
||||
class SendAnalyticsTask extends DefaultTask {
|
||||
String applicationId = 'UNKNOWN'
|
||||
String version = 'UNKNOWN'
|
||||
|
||||
@TaskAction
|
||||
def sendAnalytics() {
|
||||
try {
|
||||
def env = System.getenv()
|
||||
def disableAnalytics= env['REALM_DISABLE_ANALYTICS']
|
||||
if (disableAnalytics == null || disableAnalytics != "true") {
|
||||
send()
|
||||
}
|
||||
} catch(all) {}
|
||||
}
|
||||
|
||||
//TODO replace with properties
|
||||
private static final int READ_TIMEOUT = 2000
|
||||
private static final int CONNECT_TIMEOUT = 4000
|
||||
private static final String ADDRESS_PREFIX = "https://api.mixpanel.com/track/?data="
|
||||
private static final String ADDRESS_SUFFIX = "&ip=1"
|
||||
private static final String TOKEN = "ce0fac19508f6c8f20066d345d360fd0"
|
||||
private static final String EVENT_NAME = "Run"
|
||||
private static final String JSON_TEMPLATE = '''
|
||||
{
|
||||
"event": "%EVENT%",
|
||||
"properties": {
|
||||
"token": "%TOKEN%,
|
||||
"distinct_id": "%USER_ID%",
|
||||
"Anonymized MAC Address": "%USER_ID%",
|
||||
"Anonymized Bundle ID": "%APP_ID%",
|
||||
"Binding": "js",
|
||||
"Language": "js",
|
||||
"Framework": "react-native",
|
||||
"Virtual Machine": "jsc",
|
||||
"Realm Version": "%REALM_VERSION%",
|
||||
"Host OS Type": "%OS_TYPE%",
|
||||
"Host OS Version": "%OS_VERSION%",
|
||||
"Target OS Type": "android"
|
||||
}
|
||||
}'''
|
||||
|
||||
void send() {
|
||||
def connection = (ADDRESS_PREFIX + Utils.base64Encode(generateJson()) + ADDRESS_SUFFIX)
|
||||
.toURL().openConnection()
|
||||
connection.setConnectTimeout(CONNECT_TIMEOUT);
|
||||
connection.setReadTimeout(READ_TIMEOUT);
|
||||
connection.setRequestMethod("GET")
|
||||
connection.connect()
|
||||
connection.getResponseCode()
|
||||
}
|
||||
|
||||
private String generateJson() {
|
||||
JSON_TEMPLATE
|
||||
.replaceAll("%EVENT%", EVENT_NAME)
|
||||
.replaceAll("%TOKEN%", TOKEN)
|
||||
.replaceAll("%USER_ID%", ComputerIdentifierGenerator.get())
|
||||
.replaceAll("%APP_ID%", getAnonymousAppId())
|
||||
.replaceAll("%REALM_VERSION%", version)
|
||||
.replaceAll("%OS_TYPE%", System.getProperty("os.name"))
|
||||
.replaceAll("%OS_VERSION%", System.getProperty("os.version"))
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes an anonymous app/library id from the packages containing RealmObject classes
|
||||
* @return the anonymous app/library id
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
private String getAnonymousAppId() {
|
||||
byte[] packagesBytes = applicationId?.getBytes()
|
||||
Utils.hexStringify(Utils.sha256Hash(packagesBytes))
|
||||
}
|
||||
}
|
||||
|
||||
class ComputerIdentifierGenerator {
|
||||
private static final String UNKNOWN = "unknown";
|
||||
|
||||
private static String OS = System.getProperty("os.name").toLowerCase()
|
||||
|
||||
public static String get() {
|
||||
if (isWindows()) {
|
||||
return getWindowsIdentifier()
|
||||
} else if (isMac()) {
|
||||
return getMacOsIdentifier()
|
||||
} else if (isLinux()) {
|
||||
return getLinuxMacAddress()
|
||||
} else {
|
||||
return UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isWindows() {
|
||||
OS.contains("win")
|
||||
}
|
||||
|
||||
private static boolean isMac() {
|
||||
OS.contains("mac")
|
||||
}
|
||||
|
||||
private static boolean isLinux() {
|
||||
OS.contains("inux")
|
||||
}
|
||||
|
||||
private static String getLinuxMacAddress() {
|
||||
File machineId = new File("/var/lib/dbus/machine-id")
|
||||
if (!machineId.exists()) {
|
||||
machineId = new File("/etc/machine-id")
|
||||
}
|
||||
if (!machineId.exists()) {
|
||||
return UNKNOWN
|
||||
}
|
||||
|
||||
Scanner scanner = null
|
||||
try {
|
||||
scanner = new Scanner(machineId)
|
||||
String id = scanner.useDelimiter("\\A").next()
|
||||
return Utils.hexStringify(Utils.sha256Hash(id.getBytes()))
|
||||
} finally {
|
||||
if (scanner != null) {
|
||||
scanner.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String getMacOsIdentifier() {
|
||||
NetworkInterface networkInterface = NetworkInterface.getByName("en0")
|
||||
byte[] hardwareAddress = networkInterface.getHardwareAddress()
|
||||
Utils.hexStringify(Utils.sha256Hash(hardwareAddress))
|
||||
}
|
||||
|
||||
private static String getWindowsIdentifier() {
|
||||
Runtime runtime = Runtime.getRuntime()
|
||||
Process process = runtime.exec(["wmic", "csproduct", "get", "UUID"])
|
||||
|
||||
String result = null
|
||||
InputStream is = process.getInputStream()
|
||||
Scanner sc = new Scanner(process.getInputStream())
|
||||
try {
|
||||
while (sc.hasNext()) {
|
||||
String next = sc.next()
|
||||
if (next.contains("UUID")) {
|
||||
result = sc.next().trim()
|
||||
break
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
is.close()
|
||||
}
|
||||
|
||||
result==null?UNKNOWN:Utils.hexStringify(Utils.sha256Hash(result.getBytes()))
|
||||
}
|
||||
}
|
||||
|
||||
class Utils {
|
||||
|
||||
/**
|
||||
* Encode the given string with Base64
|
||||
* @param data the string to encode
|
||||
* @return the encoded string
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
public static String base64Encode(String data) throws UnsupportedEncodingException {
|
||||
return DatatypeConverter.printBase64Binary(data.getBytes("UTF-8"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the SHA-256 hash of the given byte array
|
||||
* @param data the byte array to hash
|
||||
* @return the hashed byte array
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
public static byte[] sha256Hash(byte[] data) throws NoSuchAlgorithmException {
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
|
||||
return messageDigest.digest(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a byte array to its hex-string
|
||||
* @param data the byte array to convert
|
||||
* @return the hex-string of the byte array
|
||||
*/
|
||||
public static String hexStringify(byte[] data) {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for (byte singleByte : data) {
|
||||
stringBuilder.append(Integer.toString((singleByte & 0xff) + 0x100, 16).substring(1));
|
||||
}
|
||||
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// see https://discuss.gradle.org/t/build-gradle-cant-find-task-class-defined-in-an-apply-from-external-script/5836/2
|
||||
ext.SendAnalyticsTask = SendAnalyticsTask
|
|
@ -219,6 +219,14 @@ task publishAndroid(dependsOn: [generateVersionClass, packageReactNdkLibs], type
|
|||
'build.gradle'
|
||||
}
|
||||
}
|
||||
|
||||
// copy analytics script
|
||||
into ('/') {
|
||||
from "$projectDir/analytics_template"
|
||||
rename { String fileName ->
|
||||
'analytics.gradle'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// publishing into maven local
|
||||
|
|
|
@ -53,6 +53,26 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
String getAppId () {
|
||||
String myappId;
|
||||
try {
|
||||
String build = new File("$projectDir/../../../android/app/build.gradle").text
|
||||
def matcher = build =~ 'applicationId.*"'
|
||||
def appId = matcher.size() > 0 ? matcher[0].trim() - 'applicationId' - ~/\s/ : '';
|
||||
myappId = appId.replaceAll('"', '')
|
||||
} catch(all) {}
|
||||
return myappId
|
||||
}
|
||||
|
||||
apply from: 'analytics.gradle'
|
||||
|
||||
task send(type: SendAnalyticsTask) {
|
||||
applicationId = getAppId()
|
||||
version = "npm --silent run get-version".execute(null, projectDir).text.trim()
|
||||
}
|
||||
|
||||
preBuild.dependsOn send
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile 'org.nanohttpd:nanohttpd:2.2.0'
|
||||
|
|
|
@ -1,223 +0,0 @@
|
|||
////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright 2016 Realm 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 io.realm.react;
|
||||
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.os.Build;
|
||||
import android.util.Base64;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.net.URL;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Set;
|
||||
|
||||
// Asynchronously submits build information to Realm when the annotation
|
||||
// processor is running
|
||||
//
|
||||
// To be clear: this does *not* run when your app is in production or on
|
||||
// your end-user's devices; it will only run when you build your app from source.
|
||||
//
|
||||
// Why are we doing this? Because it helps us build a better product for you.
|
||||
// None of the data personally identifies you, your employer or your app, but it
|
||||
// *will* help us understand what Realm version you use, what host OS you use,
|
||||
// etc. Having this info will help with prioritizing our time, adding new
|
||||
// features and deprecating old features. Collecting an anonymized bundle &
|
||||
// anonymized MAC is the only way for us to count actual usage of the other
|
||||
// metrics accurately. If we don't have a way to deduplicate the info reported,
|
||||
// it will be useless, as a single developer building their app on Windows ten
|
||||
// times would report 10 times more than a single developer that only builds
|
||||
// once from Mac OS X, making the data all but useless. No one likes sharing
|
||||
// data unless it's necessary, we get it, and we've debated adding this for a
|
||||
// long long time. Since Realm is a free product without an email signup, we
|
||||
// feel this is a necessary step so we can collect relevant data to build a
|
||||
// better product for you.
|
||||
//
|
||||
// Currently the following information is reported:
|
||||
// - What kind of JavaScript framework is being used (e.g. React Native)
|
||||
// - What kind of JavaScript VM is being used (e.g. JavaScriptCore or V8)
|
||||
// - What version of Realm is being used
|
||||
// - What OS you are running on
|
||||
// - An anonymized MAC address and bundle ID to aggregate the other information on.
|
||||
public class RealmAnalytics {
|
||||
private static RealmAnalytics instance;
|
||||
private static final int READ_TIMEOUT = 2000;
|
||||
private static final int CONNECT_TIMEOUT = 4000;
|
||||
private static final String ADDRESS_PREFIX = "https://api.mixpanel.com/track/?data=";
|
||||
private static final String ADDRESS_SUFFIX = "&ip=1";
|
||||
private static final String TOKEN = "ce0fac19508f6c8f20066d345d360fd0";
|
||||
private static final String EVENT_NAME = "Run";
|
||||
private static final String JSON_TEMPLATE
|
||||
= "{\n"
|
||||
+ " \"event\": \"%EVENT%\",\n"
|
||||
+ " \"properties\": {\n"
|
||||
+ " \"token\": \"%TOKEN%\",\n"
|
||||
+ " \"distinct_id\": \"%USER_ID%\",\n"
|
||||
+ " \"Anonymized MAC Address\": \"%USER_ID%\",\n"
|
||||
+ " \"Anonymized Bundle ID\": \"%APP_ID%\",\n"
|
||||
+ " \"Binding\": \"js\",\n"
|
||||
+ " \"Language\": \"js\",\n"
|
||||
+ " \"Framework\": \"react-native\",\n"
|
||||
+ " \"Virtual Machine\": \"jsc\",\n"
|
||||
+ " \"Realm Version\": \"%REALM_VERSION%\",\n"
|
||||
+ " \"Host OS Type\": \"%OS_TYPE%\",\n"
|
||||
+ " \"Host OS Version\": \"%OS_VERSION%\",\n"
|
||||
+ " \"Target OS Type\": \"android\"\n"
|
||||
+ " }\n"
|
||||
+ "}";
|
||||
|
||||
private ApplicationInfo applicationInfo;
|
||||
|
||||
private RealmAnalytics(ApplicationInfo applicationInfo) {
|
||||
this.applicationInfo = applicationInfo;
|
||||
}
|
||||
|
||||
public static RealmAnalytics getInstance(ApplicationInfo applicationInfo) {
|
||||
if (instance == null) {
|
||||
instance = new RealmAnalytics(applicationInfo);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static boolean shouldExecute() {
|
||||
return System.getenv("REALM_DISABLE_ANALYTICS") == null && isRunningOnEmulator();
|
||||
}
|
||||
|
||||
private void send() {
|
||||
try {
|
||||
URL url = getUrl();
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("GET");
|
||||
connection.connect();
|
||||
connection.getResponseCode();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public void execute() {
|
||||
Thread backgroundThread = new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
send();
|
||||
}
|
||||
});
|
||||
backgroundThread.start();
|
||||
try {
|
||||
backgroundThread.join(CONNECT_TIMEOUT + READ_TIMEOUT);
|
||||
} catch (InterruptedException ignored) {
|
||||
// We ignore this exception on purpose not to break the build system if this class fails
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
// We ignore this exception on purpose not to break the build system if this class fails
|
||||
}
|
||||
}
|
||||
|
||||
private URL getUrl() throws
|
||||
MalformedURLException,
|
||||
SocketException,
|
||||
NoSuchAlgorithmException,
|
||||
UnsupportedEncodingException {
|
||||
return new URL(ADDRESS_PREFIX + base64Encode(generateJson()) + ADDRESS_SUFFIX);
|
||||
}
|
||||
|
||||
private String generateJson() throws SocketException, NoSuchAlgorithmException {
|
||||
return JSON_TEMPLATE
|
||||
.replaceAll("%EVENT%", EVENT_NAME)
|
||||
.replaceAll("%TOKEN%", TOKEN)
|
||||
.replaceAll("%USER_ID%", getAnonymousUserId())
|
||||
.replaceAll("%APP_ID%", getAnonymousAppId())
|
||||
.replaceAll("%REALM_VERSION%", Version.VERSION)
|
||||
.replaceAll("%OS_TYPE%", System.getProperty("os.name"))
|
||||
.replaceAll("%OS_VERSION%", System.getProperty("os.version"));
|
||||
}
|
||||
|
||||
public static boolean isRunningOnEmulator() {
|
||||
// Check if running in Genymotion or on the stock emulator.
|
||||
return Build.FINGERPRINT.contains("vbox") || Build.FINGERPRINT.contains("generic");
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute an anonymous user id from the hashed MAC address of the first network interface
|
||||
* @return the anonymous user id
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws SocketException
|
||||
*/
|
||||
private static String getAnonymousUserId() throws NoSuchAlgorithmException, SocketException {
|
||||
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
|
||||
|
||||
if (!networkInterfaces.hasMoreElements()) {
|
||||
throw new IllegalStateException("No network interfaces detected");
|
||||
}
|
||||
|
||||
NetworkInterface networkInterface = networkInterfaces.nextElement();
|
||||
byte[] hardwareAddress = networkInterface.getHardwareAddress(); // Normally this is the MAC address
|
||||
|
||||
return hexStringify(sha256Hash(hardwareAddress));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute an anonymous app/library id from the packages containing RealmObject classes
|
||||
* @return the anonymous app/library id
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
private String getAnonymousAppId() throws NoSuchAlgorithmException {
|
||||
byte[] packagesBytes = applicationInfo.packageName.getBytes();
|
||||
|
||||
return hexStringify(sha256Hash(packagesBytes));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the given string with Base64
|
||||
* @param data the string to encode
|
||||
* @return the encoded string
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
private static String base64Encode(String data) throws UnsupportedEncodingException {
|
||||
return Base64.encodeToString(data.getBytes("UTF-8"), Base64.NO_WRAP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the SHA-256 hash of the given byte array
|
||||
* @param data the byte array to hash
|
||||
* @return the hashed byte array
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
private static byte[] sha256Hash(byte[] data) throws NoSuchAlgorithmException {
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
|
||||
return messageDigest.digest(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a byte array to its hex-string
|
||||
* @param data the byte array to convert
|
||||
* @return the hex-string of the byte array
|
||||
*/
|
||||
private static String hexStringify(byte[] data) {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for (byte singleByte : data) {
|
||||
stringBuilder.append(Integer.toString((singleByte & 0xff) + 0x100, 16).substring(1));
|
||||
}
|
||||
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package io.realm.react;
|
||||
|
||||
import android.content.res.AssetManager;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
|
@ -21,7 +22,7 @@ import java.util.Map;
|
|||
|
||||
import fi.iki.elonen.NanoHTTPD;
|
||||
|
||||
public class RealmReactModule extends ReactContextBaseJavaModule {
|
||||
class RealmReactModule extends ReactContextBaseJavaModule {
|
||||
private static final int DEFAULT_PORT = 8082;
|
||||
private static boolean sentAnalytics = false;
|
||||
|
||||
|
@ -49,14 +50,6 @@ public class RealmReactModule extends ReactContextBaseJavaModule {
|
|||
}
|
||||
|
||||
setDefaultRealmFileDirectory(fileDir, assetManager);
|
||||
|
||||
// Attempt to send analytics info only once, and only if allowed to do so.
|
||||
if (!sentAnalytics && RealmAnalytics.shouldExecute()) {
|
||||
sentAnalytics = true;
|
||||
|
||||
RealmAnalytics analytics = RealmAnalytics.getInstance(reactContext.getApplicationInfo());
|
||||
analytics.execute();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -74,7 +67,7 @@ public class RealmReactModule extends ReactContextBaseJavaModule {
|
|||
startWebServer();
|
||||
|
||||
List<String> hosts;
|
||||
if (RealmAnalytics.isRunningOnEmulator()) {
|
||||
if (isRunningOnEmulator()) {
|
||||
hosts = Arrays.asList(new String[]{"localhost"});
|
||||
} else {
|
||||
hosts = getIPAddresses();
|
||||
|
@ -92,6 +85,11 @@ public class RealmReactModule extends ReactContextBaseJavaModule {
|
|||
stopWebServer();
|
||||
}
|
||||
|
||||
private static boolean isRunningOnEmulator() {
|
||||
// Check if running in Genymotion or on the stock emulator.
|
||||
return Build.FINGERPRINT.contains("vbox") || Build.FINGERPRINT.contains("generic");
|
||||
}
|
||||
|
||||
private List<String> getIPAddresses() {
|
||||
ArrayList<String> ipAddresses = new ArrayList<String>();
|
||||
|
||||
|
|
|
@ -24,4 +24,4 @@ public class RealmReactPackage implements ReactPackage {
|
|||
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue