package com.learnium.RNDeviceInfo;

import android.Manifest;
import android.annotation.SuppressLint;
import android.app.KeyguardManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.FeatureInfo;
import android.location.LocationManager;
import android.media.AudioManager;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiInfo;
import android.os.Build;
import android.os.Environment;
import android.os.PowerManager;
import android.os.StatFs;
import android.os.BatteryManager;
import android.provider.Settings;
import android.webkit.WebSettings;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.app.ActivityManager;
import android.hardware.Camera;
import android.hardware.camera2.CameraManager;

import androidx.annotation.Nullable;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.learnium.RNDeviceInfo.resolver.DeviceIdResolver;
import com.learnium.RNDeviceInfo.resolver.DeviceTypeResolver;

import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.lang.Runtime;
import java.net.NetworkInterface;
import java.math.BigInteger;
import java.util.Locale;
import java.util.Map;

import javax.annotation.Nonnull;

import static android.os.BatteryManager.BATTERY_STATUS_CHARGING;
import static android.os.BatteryManager.BATTERY_STATUS_FULL;
import static android.provider.Settings.Secure.getString;

@ReactModule(name = RNDeviceModule.NAME)
public class RNDeviceModule extends ReactContextBaseJavaModule {
  public static final String NAME = "RNDeviceInfo";
  private final DeviceTypeResolver deviceTypeResolver;
  private final DeviceIdResolver deviceIdResolver;
  private BroadcastReceiver receiver;
  private RNInstallReferrerClient installReferrerClient;

  private double mLastBatteryLevel = -1;
  private String sLastBatteryState = "";

  private static String BATTERY_STATE = "batteryState";
  private static String BATTERY_LEVEL= "batteryLevel";
  private static String LOW_POWER_MODE = "lowPowerMode";

  public RNDeviceModule(ReactApplicationContext reactContext) {
    super(reactContext);
    this.deviceTypeResolver = new DeviceTypeResolver(reactContext);
    this.deviceIdResolver = new DeviceIdResolver(reactContext);
    this.installReferrerClient = new RNInstallReferrerClient(reactContext.getBaseContext());
  }

  @Override
  public void initialize() {
    IntentFilter filter = new IntentFilter();
    filter.addAction(Intent.ACTION_BATTERY_CHANGED);

    receiver = new BroadcastReceiver() {
      @Override
      public void onReceive(Context context, Intent intent) {
        WritableMap powerState = getPowerStateFromIntent(intent);

        if(powerState == null) {
          return;
        }

        String batteryState = powerState.getString(BATTERY_STATE);
        Double batteryLevel = powerState.getDouble(BATTERY_LEVEL);

        if(!sLastBatteryState.equalsIgnoreCase(batteryState)) {
          sendEvent(getReactApplicationContext(), "RNDeviceInfo_powerStateDidChange", batteryState);
          sLastBatteryState = batteryState;
        }

        if(mLastBatteryLevel != batteryLevel) {
            sendEvent(getReactApplicationContext(), "RNDeviceInfo_batteryLevelDidChange", batteryLevel);

          if(batteryLevel <= .15) {
            sendEvent(getReactApplicationContext(), "RNDeviceInfo_batteryLevelIsLow", batteryLevel);
          }

          mLastBatteryLevel = batteryLevel;
        }
      }
    };

    getReactApplicationContext().registerReceiver(receiver, filter);
  }


  @Override
  public void onCatalystInstanceDestroy() {
    getReactApplicationContext().unregisterReceiver(receiver);
  }


  @Override
  @Nonnull
  public String getName() {
    return NAME;
  }

  @SuppressLint("MissingPermission")
  private WifiInfo getWifiInfo() {
    WifiManager manager = (WifiManager) getReactApplicationContext().getApplicationContext().getSystemService(Context.WIFI_SERVICE);
    if (manager != null) {
      return manager.getConnectionInfo();
    }
    return null;
  }

  @Override
  public Map<String, Object> getConstants() {
    String appVersion, buildNumber, appName;

    try {
      appVersion = getPackageInfo().versionName;
      buildNumber = Integer.toString(getPackageInfo().versionCode);
      appName = getReactApplicationContext().getApplicationInfo().loadLabel(getReactApplicationContext().getPackageManager()).toString();
    } catch (Exception e) {
      appVersion = "unknown";
      buildNumber = "unknown";
      appName = "unknown";
    }

    final Map<String, Object> constants = new HashMap<>();

    constants.put("uniqueId", getUniqueIdSync());
    constants.put("deviceId", Build.BOARD);
    constants.put("bundleId", getReactApplicationContext().getPackageName());
    constants.put("systemName", "Android");
    constants.put("systemVersion", Build.VERSION.RELEASE);
    constants.put("appVersion", appVersion);
    constants.put("buildNumber", buildNumber);
    constants.put("isTablet", deviceTypeResolver.isTablet());
    constants.put("appName", appName);
    constants.put("brand", Build.BRAND);
    constants.put("model", Build.MODEL);
    constants.put("deviceType", deviceTypeResolver.getDeviceType().getValue());

    return constants;
  }

  @ReactMethod
  public void isEmulator(Promise p) {
    p.resolve(isEmulatorSync());
  }

  @SuppressLint("HardwareIds")
  @ReactMethod(isBlockingSynchronousMethod = true)
  public boolean isEmulatorSync() {
    return Build.FINGERPRINT.startsWith("generic")
            || Build.FINGERPRINT.startsWith("unknown")
            || Build.MODEL.contains("google_sdk")
            || Build.MODEL.toLowerCase(Locale.ROOT).contains("droid4x")
            || Build.MODEL.contains("Emulator")
            || Build.MODEL.contains("Android SDK built for x86")
            || Build.MANUFACTURER.contains("Genymotion")
            || Build.HARDWARE.contains("goldfish")
            || Build.HARDWARE.contains("ranchu")
            || Build.HARDWARE.contains("vbox86")
            || Build.PRODUCT.contains("sdk")
            || Build.PRODUCT.contains("google_sdk")
            || Build.PRODUCT.contains("sdk_google")
            || Build.PRODUCT.contains("sdk_x86")
            || Build.PRODUCT.contains("vbox86p")
            || Build.PRODUCT.contains("emulator")
            || Build.PRODUCT.contains("simulator")
            || Build.BOARD.toLowerCase(Locale.ROOT).contains("nox")
            || Build.BOOTLOADER.toLowerCase(Locale.ROOT).contains("nox")
            || Build.HARDWARE.toLowerCase(Locale.ROOT).contains("nox")
            || Build.PRODUCT.toLowerCase(Locale.ROOT).contains("nox")
            || Build.SERIAL.toLowerCase(Locale.ROOT).contains("nox")
            || (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"));
  }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public float getFontScaleSync() { return getReactApplicationContext().getResources().getConfiguration().fontScale; }
  @ReactMethod
  public void getFontScale(Promise p) { p.resolve(getFontScaleSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public boolean isPinOrFingerprintSetSync() {
    KeyguardManager keyguardManager = (KeyguardManager) getReactApplicationContext().getSystemService(Context.KEYGUARD_SERVICE);
    if (keyguardManager != null) {
      return keyguardManager.isKeyguardSecure();
    }
    System.err.println("Unable to determine keyguard status. KeyguardManager was null");
    return false;
  }
  @ReactMethod
  public void isPinOrFingerprintSet(Promise p) { p.resolve(isPinOrFingerprintSetSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  @SuppressWarnings("ConstantConditions")
  public String getIpAddressSync() {
    try {
      return
              InetAddress.getByAddress(
                      ByteBuffer
                              .allocate(4)
                              .order(ByteOrder.LITTLE_ENDIAN)
                              .putInt(getWifiInfo().getIpAddress())
                              .array())
                      .getHostAddress();
    } catch (Exception e) {
      return "unknown";
    }
  }

  @ReactMethod
  public void getIpAddress(Promise p) { p.resolve(getIpAddressSync()); }

  @SuppressWarnings("deprecation")
  @ReactMethod(isBlockingSynchronousMethod = true)
  public boolean isCameraPresentSync() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      CameraManager manager=(CameraManager)getReactApplicationContext().getSystemService(Context.CAMERA_SERVICE);
      try {
        return manager.getCameraIdList().length > 0;
      } catch (Exception e) {
        return false;
      }
    } else {
      return Camera.getNumberOfCameras()> 0;
    }
  }
  @ReactMethod
  public void isCameraPresent(Promise p) { p.resolve(isCameraPresentSync()); }

  @SuppressLint("HardwareIds")
  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getMacAddressSync() {
    WifiInfo wifiInfo = getWifiInfo();
    String macAddress = "";
    if (wifiInfo != null) {
      macAddress = wifiInfo.getMacAddress();
    }

    String permission = "android.permission.INTERNET";
    int res = getReactApplicationContext().checkCallingOrSelfPermission(permission);

    if (res == PackageManager.PERMISSION_GRANTED) {
      try {
        List<NetworkInterface> all = Collections.list(NetworkInterface.getNetworkInterfaces());
        for (NetworkInterface nif : all) {
          if (!nif.getName().equalsIgnoreCase("wlan0")) continue;

          byte[] macBytes = nif.getHardwareAddress();
          if (macBytes == null) {
            macAddress = "";
          } else {

            StringBuilder res1 = new StringBuilder();
            for (byte b : macBytes) {
              res1.append(String.format("%02X:",b));
            }

            if (res1.length() > 0) {
              res1.deleteCharAt(res1.length() - 1);
            }

            macAddress = res1.toString();
          }
        }
      } catch (Exception ex) {
        // do nothing
      }
    }

    return macAddress;
  }

  @ReactMethod
  public void getMacAddress(Promise p) { p.resolve(getMacAddressSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getCarrierSync() {
    TelephonyManager telMgr = (TelephonyManager) getReactApplicationContext().getSystemService(Context.TELEPHONY_SERVICE);
    if (telMgr != null) {
      return telMgr.getNetworkOperatorName();
    } else {
      System.err.println("Unable to get network operator name. TelephonyManager was null");
      return "unknown";
    }
  }
  @ReactMethod
  public void getCarrier(Promise p) { p.resolve(getCarrierSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public double getTotalDiskCapacitySync() {
    try {
      StatFs root = new StatFs(Environment.getRootDirectory().getAbsolutePath());
      return BigInteger.valueOf(root.getBlockCount()).multiply(BigInteger.valueOf(root.getBlockSize())).doubleValue();
    } catch (Exception e) {
      return -1;
    }
  }
  @ReactMethod
  public void getTotalDiskCapacity(Promise p) { p.resolve(getTotalDiskCapacitySync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public double getFreeDiskStorageSync() {
    try {
      StatFs external = new StatFs(Environment.getExternalStorageDirectory().getAbsolutePath());
      long availableBlocks;
      long blockSize;

      if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
        availableBlocks = external.getAvailableBlocks();
        blockSize = external.getBlockSize();
      } else {
        availableBlocks = external.getAvailableBlocksLong();
        blockSize = external.getBlockSizeLong();
      }

      return BigInteger.valueOf(availableBlocks).multiply(BigInteger.valueOf(blockSize)).doubleValue();
    } catch (Exception e) {
      return -1;
    }
  }
  @ReactMethod
  public void getFreeDiskStorage(Promise p) { p.resolve(getFreeDiskStorageSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public boolean isBatteryChargingSync(){
    IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
    Intent batteryStatus = getReactApplicationContext().registerReceiver(null, ifilter);
    int status = 0;
    if (batteryStatus != null) {
      status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
    }
    return status == BATTERY_STATUS_CHARGING;
  }
  @ReactMethod
  public void isBatteryCharging(Promise p) { p.resolve(isBatteryChargingSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public int getUsedMemorySync() {
    Runtime rt = Runtime.getRuntime();
    long usedMemory = rt.totalMemory() - rt.freeMemory();
    return (int)usedMemory;
  }
  @ReactMethod
  public void getUsedMemory(Promise p) { p.resolve(getUsedMemorySync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public WritableMap getPowerStateSync() {
    Intent intent = getReactApplicationContext().registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
    return getPowerStateFromIntent(intent);
  }

  @ReactMethod
  public void getPowerState(Promise p) { p.resolve(getPowerStateSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public double getBatteryLevelSync() {
    Intent intent = getReactApplicationContext().registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
    WritableMap powerState = getPowerStateFromIntent(intent);

    if(powerState == null) {
      return 0;
    }

    return powerState.getDouble(BATTERY_LEVEL);
  }

  @ReactMethod
  public void getBatteryLevel(Promise p) { p.resolve(getBatteryLevelSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public boolean isAirplaneModeSync() {
    boolean isAirplaneMode;
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
      isAirplaneMode = Settings.System.getInt(getReactApplicationContext().getContentResolver(),Settings.System.AIRPLANE_MODE_ON, 0) != 0;
    } else {
      isAirplaneMode = Settings.Global.getInt(getReactApplicationContext().getContentResolver(),Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
    }
    return isAirplaneMode;
  }
  @ReactMethod
  public void isAirplaneMode(Promise p) { p.resolve(isAirplaneModeSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public boolean hasSystemFeatureSync(String feature) {
    if (feature == null || feature.equals("")) {
      return false;
    }

    return getReactApplicationContext().getPackageManager().hasSystemFeature(feature);
  }
  @ReactMethod
  public void hasSystemFeature(String feature, Promise p) { p.resolve(hasSystemFeatureSync(feature)); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public WritableArray getSystemAvailableFeaturesSync() {
    final FeatureInfo[] featureList = getReactApplicationContext().getPackageManager().getSystemAvailableFeatures();

    WritableArray promiseArray = Arguments.createArray();
    for (FeatureInfo f : featureList) {
      if (f.name != null) {
        promiseArray.pushString(f.name);
      }
    }

    return promiseArray;
  }
  @ReactMethod
  public void getSystemAvailableFeatures(Promise p) { p.resolve(getSystemAvailableFeaturesSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public boolean isLocationEnabledSync() {
    boolean locationEnabled;

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
      LocationManager mLocationManager = (LocationManager) getReactApplicationContext().getSystemService(Context.LOCATION_SERVICE);
      try {
        locationEnabled = mLocationManager.isLocationEnabled();
      } catch (Exception e) {
        System.err.println("Unable to determine if location enabled. LocationManager was null");
        return false;
      }
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
      int locationMode = Settings.Secure.getInt(getReactApplicationContext().getContentResolver(), Settings.Secure.LOCATION_MODE, Settings.Secure.LOCATION_MODE_OFF);
      locationEnabled = locationMode != Settings.Secure.LOCATION_MODE_OFF;
    } else {
      String locationProviders = getString(getReactApplicationContext().getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
      locationEnabled = !TextUtils.isEmpty(locationProviders);
    }

    return locationEnabled;
  }
  @ReactMethod
  public void isLocationEnabled(Promise p) { p.resolve(isLocationEnabledSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public boolean isHeadphonesConnectedSync() {
    AudioManager audioManager = (AudioManager)getReactApplicationContext().getSystemService(Context.AUDIO_SERVICE);
    return audioManager.isWiredHeadsetOn() || audioManager.isBluetoothA2dpOn();
  }
  @ReactMethod
  public void isHeadphonesConnected(Promise p) {p.resolve(isHeadphonesConnectedSync());}

  @ReactMethod(isBlockingSynchronousMethod = true)
  public WritableMap getAvailableLocationProvidersSync() {
    LocationManager mLocationManager = (LocationManager) getReactApplicationContext().getSystemService(Context.LOCATION_SERVICE);
    WritableMap providersAvailability = Arguments.createMap();
    try {
      List<String> providers = mLocationManager.getProviders(false);
      for (String provider : providers) {
        providersAvailability.putBoolean(provider, mLocationManager.isProviderEnabled(provider));
      }
    } catch (Exception e) {
      System.err.println("Unable to get location providers. LocationManager was null");
    }

    return providersAvailability;
  }
  @ReactMethod
  public void getAvailableLocationProviders(Promise p) { p.resolve(getAvailableLocationProvidersSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getInstallReferrerSync() {
    SharedPreferences sharedPref = getReactApplicationContext().getSharedPreferences("react-native-device-info", Context.MODE_PRIVATE);
    return sharedPref.getString("installReferrer", Build.UNKNOWN);
  }
  @ReactMethod
  public void getInstallReferrer(Promise p) { p.resolve(getInstallReferrerSync()); }

  private PackageInfo getPackageInfo() throws Exception {
    return getReactApplicationContext().getPackageManager().getPackageInfo(getReactApplicationContext().getPackageName(), 0);
  }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getInstallerPackageNameSync() {
    String packageName = getReactApplicationContext().getPackageName();
    String installerPackageName = getReactApplicationContext().getPackageManager().getInstallerPackageName(packageName);

    if (installerPackageName == null) {
      return "unknown";
    }

    return installerPackageName;
  }

  @ReactMethod
  public void getInstallerPackageName(Promise p) { p.resolve(getInstallerPackageNameSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public double getFirstInstallTimeSync() {
    try {
      return (double)getPackageInfo().firstInstallTime;
    } catch (Exception e) {
      return -1;
    }
  }
  @ReactMethod
  public void getFirstInstallTime(Promise p) { p.resolve(getFirstInstallTimeSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public double getLastUpdateTimeSync() {
    try {
      return (double)getPackageInfo().lastUpdateTime;
    } catch (Exception e) {
      return -1;
    }
  }
  @ReactMethod
  public void getLastUpdateTime(Promise p) { p.resolve(getLastUpdateTimeSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getDeviceNameSync() {
    try {
      String bluetoothName = Settings.Secure.getString(getReactApplicationContext().getContentResolver(), "bluetooth_name");
      if (bluetoothName != null) {
        return bluetoothName;
      }

      if (Build.VERSION.SDK_INT >= 25) {
        String deviceName = Settings.Global.getString(getReactApplicationContext().getContentResolver(), Settings.Global.DEVICE_NAME);
        if (deviceName != null) {
          return deviceName;
        }
      }
    } catch (Exception e) {
      // same as default unknown return
    }
    return "unknown";
  }
  @ReactMethod
  public void getDeviceName(Promise p) { p.resolve(getDeviceNameSync()); }

  @SuppressLint({"HardwareIds", "MissingPermission"})
  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getSerialNumberSync() {
    try {
      if (Build.VERSION.SDK_INT >= 26) {
        if (getReactApplicationContext().checkCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
          return Build.getSerial();
        }
      }
    } catch (Exception e) {
      // This is almost always a PermissionException. We will log it but return unknown
      System.err.println("getSerialNumber failed, it probably should not be used: " + e.getMessage());
    }

    return "unknown";
  }
  @ReactMethod
  public void getSerialNumber(Promise p) { p.resolve(getSerialNumberSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getDeviceSync() {  return Build.DEVICE; }
  @ReactMethod
  public void getDevice(Promise p) { p.resolve(getDeviceSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getBuildIdSync() { return Build.ID; }
  @ReactMethod
  public void getBuildId(Promise p) { p.resolve(getBuildIdSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public int getApiLevelSync() { return Build.VERSION.SDK_INT; }
  @ReactMethod
  public void getApiLevel(Promise p) { p.resolve(getApiLevelSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getBootloaderSync() { return Build.BOOTLOADER; }
  @ReactMethod
  public void getBootloader(Promise p) { p.resolve(getBootloaderSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getDisplaySync() { return Build.DISPLAY; }
  @ReactMethod
  public void getDisplay(Promise p) { p.resolve(getDisplaySync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getFingerprintSync() { return Build.FINGERPRINT; }
  @ReactMethod
  public void getFingerprint(Promise p) { p.resolve(getFingerprintSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getHardwareSync() { return Build.HARDWARE; }
  @ReactMethod
  public void getHardware(Promise p) { p.resolve(getHardwareSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getHostSync() { return Build.HOST; }
  @ReactMethod
  public void getHost(Promise p) { p.resolve(getHostSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getProductSync() { return Build.PRODUCT; }
  @ReactMethod
  public void getProduct(Promise p) { p.resolve(getProductSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getTagsSync() { return Build.TAGS; }
  @ReactMethod
  public void getTags(Promise p) { p.resolve(getTagsSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getTypeSync() { return Build.TYPE; }
  @ReactMethod
  public void getType(Promise p) { p.resolve(getTypeSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getSystemManufacturerSync() { return Build.MANUFACTURER; }
  @ReactMethod
  public void getSystemManufacturer(Promise p) { p.resolve(getSystemManufacturerSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getCodenameSync() { return Build.VERSION.CODENAME; }
  @ReactMethod
  public void getCodename(Promise p) { p.resolve(getCodenameSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getIncrementalSync() { return Build.VERSION.INCREMENTAL; }
  @ReactMethod
  public void getIncremental(Promise p) { p.resolve(getIncrementalSync()); }

  @SuppressLint("HardwareIds")
  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getUniqueIdSync() { return getString(getReactApplicationContext().getContentResolver(), Settings.Secure.ANDROID_ID); }

  @SuppressLint("HardwareIds")
  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getAndroidIdSync() { return getUniqueIdSync(); }
  @ReactMethod
  public void getAndroidId(Promise p) { p.resolve(getAndroidIdSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public double getMaxMemorySync() { return (double)Runtime.getRuntime().maxMemory(); }
  @ReactMethod
  public void getMaxMemory(Promise p) { p.resolve(getMaxMemorySync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public double getTotalMemorySync() {
    ActivityManager actMgr = (ActivityManager) getReactApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
    ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
    if (actMgr != null) {
      actMgr.getMemoryInfo(memInfo);
    } else {
      System.err.println("Unable to getMemoryInfo. ActivityManager was null");
      return -1;
    }
    return (double)memInfo.totalMem;
  }
  @ReactMethod
  public void getTotalMemory(Promise p) { p.resolve(getTotalMemorySync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  @SuppressWarnings({"ConstantConditions", "deprecation"})
  public String getInstanceIdSync() {
    return deviceIdResolver.getInstanceIdSync();
  }
  @ReactMethod
  public void getInstanceId(Promise p) { p.resolve(getInstanceIdSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getBaseOsSync() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
      return Build.VERSION.BASE_OS;
    }
    return "unknown";
  }
  @ReactMethod
  public void getBaseOs(Promise p) { p.resolve(getBaseOsSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getPreviewSdkIntSync() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
      return Integer.toString(Build.VERSION.PREVIEW_SDK_INT);
    }
    return "unknown";
  }
  @ReactMethod
  public void getPreviewSdkInt(Promise p) { p.resolve(getPreviewSdkIntSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getSecurityPatchSync() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
      return Build.VERSION.SECURITY_PATCH;
    }
    return "unknown";
  }
  @ReactMethod
  public void getSecurityPatch(Promise p) { p.resolve(getSecurityPatchSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getUserAgentSync() {
    try {
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
        return WebSettings.getDefaultUserAgent(getReactApplicationContext());
      } else {
        return System.getProperty("http.agent");
      }
    } catch (RuntimeException e) {
      return System.getProperty("http.agent");
    }
  }
  @ReactMethod
  public void getUserAgent(Promise p) { p.resolve(getUserAgentSync()); }

  @SuppressLint({"HardwareIds", "MissingPermission"})
  @ReactMethod(isBlockingSynchronousMethod = true)
  public String getPhoneNumberSync() {
    if (getReactApplicationContext() != null &&
            (getReactApplicationContext().checkCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED ||
                    (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && getReactApplicationContext().checkCallingOrSelfPermission(Manifest.permission.READ_SMS) == PackageManager.PERMISSION_GRANTED) ||
                    (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && getReactApplicationContext().checkCallingOrSelfPermission(Manifest.permission.READ_PHONE_NUMBERS) == PackageManager.PERMISSION_GRANTED))) {
      TelephonyManager telMgr = (TelephonyManager) getReactApplicationContext().getSystemService(Context.TELEPHONY_SERVICE);
      if (telMgr != null) {
        return telMgr.getLine1Number();
      } else {
        System.err.println("Unable to getPhoneNumber. TelephonyManager was null");
      }
    }
    return "unknown";
  }
  @ReactMethod
  public void getPhoneNumber(Promise p) { p.resolve(getPhoneNumberSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public WritableArray getSupportedAbisSync() {
    WritableArray array = new WritableNativeArray();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      for (String abi : Build.SUPPORTED_ABIS) {
        array.pushString(abi);
      }
    } else {
      array.pushString(Build.CPU_ABI);
    }
    return array;
  }
  @ReactMethod
  public void getSupportedAbis(Promise p) { p.resolve(getSupportedAbisSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public WritableArray getSupported32BitAbisSync() {
    WritableArray array = new WritableNativeArray();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      for (String abi : Build.SUPPORTED_32_BIT_ABIS) {
        array.pushString(abi);
      }
    }
    return array;
  }
  @ReactMethod
  public void getSupported32BitAbis(Promise p) { p.resolve(getSupported32BitAbisSync()); }

  @ReactMethod(isBlockingSynchronousMethod = true)
  public WritableArray getSupported64BitAbisSync() {
    WritableArray array = new WritableNativeArray();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      for (String abi : Build.SUPPORTED_64_BIT_ABIS) {
        array.pushString(abi);
      }
    }
    return array;
  }
  @ReactMethod
  public void getSupported64BitAbis(Promise p) { p.resolve(getSupported64BitAbisSync()); }


  private WritableMap getPowerStateFromIntent (Intent intent) {
    if(intent == null) {
      return null;
    }

    int batteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
    int batteryScale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
    int isPlugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
    int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);

    float batteryPercentage = batteryLevel / (float)batteryScale;

    String batteryState = "unknown";

    if(isPlugged == 0) {
      batteryState = "unplugged";
    } else if(status == BATTERY_STATUS_CHARGING) {
      batteryState = "charging";
    } else if(status == BATTERY_STATUS_FULL) {
      batteryState = "full";
    }

    PowerManager powerManager = (PowerManager)getReactApplicationContext().getSystemService(Context.POWER_SERVICE);
    boolean powerSaveMode = false;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
      powerSaveMode = powerManager.isPowerSaveMode();
    }

    WritableMap powerState = Arguments.createMap();
    powerState.putString(BATTERY_STATE, batteryState);
    powerState.putDouble(BATTERY_LEVEL, batteryPercentage);
    powerState.putBoolean(LOW_POWER_MODE, powerSaveMode);

    return powerState;
  }

  private void sendEvent(ReactContext reactContext,
                         String eventName,
                         @Nullable Object data) {
    reactContext
            .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
            .emit(eventName, data);
  }
}