content: package com.amplitude.api;
  
  import android.content.ContentResolver;
  import android.content.Context;
  import android.content.pm.PackageInfo;
  import android.content.pm.PackageManager.NameNotFoundException;
  import android.content.res.Configuration;
  import android.content.res.Resources;
  import android.location.Address;
  import android.location.Geocoder;
  import android.location.Location;
  import android.location.LocationManager;
  import android.os.Build;
  import android.os.LocaleList;
  import android.provider.Settings.Secure;
  import android.telephony.TelephonyManager;
  
  import java.io.IOException;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.Locale;
  import java.util.UUID;
  
  @SuppressWarnings("MissingPermission")
  public class DeviceInfo {
  
      private static final String TAG = DeviceInfo.class.getName();
  
      public static final String OS_NAME = "android";
  
      private static final String SETTING_LIMIT_AD_TRACKING = "limit_ad_tracking";
      private static final String SETTING_ADVERTISING_ID = "advertising_id";
  
      private boolean locationListening;
  
      private boolean shouldTrackAdid;
  
      private Context context;
  
      private CachedInfo cachedInfo;
  
      /**
       * Internal class serves as a cache
       */
      private class CachedInfo {
          private String advertisingId;
          private String country;
          private String versionName;
          private String osName;
          private String osVersion;
          private String brand;
          private String manufacturer;
          private String model;
          private String carrier;
          private String language;
          private boolean limitAdTrackingEnabled;
          private boolean gpsEnabled; // google play services
          private String appSetId;
  
          private CachedInfo() {
              advertisingId = getAdvertisingId();
              versionName = getVersionName();
              osName = getOsName();
              osVersion = getOsVersion();
              brand = getBrand();
              manufacturer = getManufacturer();
              model = getModel();
              carrier = getCarrier();
              country = getCountry();
              language = getLanguage();
              gpsEnabled = checkGPSEnabled();
              appSetId = getAppSetId();
          }
  
          /**
           * Internal methods for getting raw information
           */
  
          private String getVersionName() {
              PackageInfo packageInfo;
              try {
                  packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
                  return packageInfo.versionName;
              } catch (NameNotFoundException e) {
  
              } catch (Exception e) {
                  
              }
              return null;
          }
  
          private String getOsName() {
              return OS_NAME;
          }
  
          private String getOsVersion() {
              return Build.VERSION.RELEASE;
          }
  
          private String getBrand() {
              return Build.BRAND;
          }
  
          private String getManufacturer() {
              return Build.MANUFACTURER;
          }
  
          private String getModel() {
              return Build.MODEL;
          }
  
          private String getCarrier() {
              try {
                  TelephonyManager manager = (TelephonyManager) context
                          .getSystemService(Context.TELEPHONY_SERVICE);
                  return manager.getNetworkOperatorName();
              } catch (Exception e) {
                  // Failed to get network operator name from network
              }
              return null;
          }
  
          private String getCountry() {
              // This should not be called on the main thread.
  
              // Prioritize reverse geocode, but until we have a result from that,
              // we try to grab the country from the network, and finally the locale
              String country = getCountryFromLocation();
              if (!Utils.isEmptyString(country)) {
                  return country;
              }
  
              country = getCountryFromNetwork();
              if (!Utils.isEmptyString(country)) {
                  return country;
              }
              return getCountryFromLocale();
          }
  
          private String getCountryFromLocation() {
              if (!isLocationListening()) {
                  return null;
              }
  
              Location recent = getMostRecentLocation();
              if (recent != null) {
                  try {
                      if (Geocoder.isPresent()) {
                          Geocoder geocoder = getGeocoder();
                          List<Address> addresses = geocoder.getFromLocation(recent.getLatitude(),
                                  recent.getLongitude(), 1);
                          if (addresses != null) {
                              for (Address address : addresses) {
                                  if (address != null) {
                                      return address.getCountryCode();
                                  }
                              }
                          }
                      }
                  } catch (IOException e) {
                      // Failed to reverse geocode location
                  } catch (NullPointerException e) {
                      // Failed to reverse geocode location
                  } catch (NoSuchMethodError e) {
                      // failed to fetch geocoder
                  } catch (IllegalArgumentException e) {
                      // Bad lat / lon values can cause Geocoder to throw IllegalArgumentExceptions
                  } catch (IllegalStateException e) {
                      // sometimes the location manager is unavailable
                  } catch (SecurityException e) {
                      // Customized Android System without Google Play Service Installed
                  }
              }
              return null;
          }
  
          private String getCountryFromNetwork() {
              try {
                  TelephonyManager manager = (TelephonyManager) context
                          .getSystemService(Context.TELEPHONY_SERVICE);
                  if (manager.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA) {
                      String country = manager.getNetworkCountryIso();
                      if (country != null) {
                          return country.toUpperCase(Locale.US);
                      }
                  }
              } catch (Exception e) {
                  // Failed to get country from network
              }
              return null;
          }
  
          private Locale getLocale() {
              final Configuration configuration = Resources.getSystem().getConfiguration();
              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                  final LocaleList localeList = configuration.getLocales();
                  if (localeList.isEmpty()) {
                      return Locale.getDefault();
                  } else {
                      return localeList.get(0);
                  }
              } else {
                  return configuration.locale;
              }
          }
  
          private String getCountryFromLocale() {
              return getLocale().getCountry();
          }
  
          private String getLanguage() {
              return getLocale().getLanguage();
          }
  
          private String getAdvertisingId() {
              if (!shouldTrackAdid) {
                  return null;
              }
  
              // This should not be called on the main thread.
              if ("Amazon".equals(getManufacturer())) {
                  return getAndCacheAmazonAdvertisingId();
              } else {
                  return getAndCacheGoogleAdvertisingId();
              }
          }
  
          private String getAppSetId() {
              try {
                  Class AppSet = Class
                          .forName("com.google.android.gms.appset.AppSet");
                  Method getClient = AppSet.getMethod("getClient", Context.class);
                  Object appSetIdClient = getClient.invoke(null, context);
                  Method getAppSetIdInfo = appSetIdClient.getClass().getMethod("getAppSetIdInfo");
                  Object taskWithAppSetInfo = getAppSetIdInfo.invoke(appSetIdClient);
                  Class Tasks = Class.forName("com.google.android.gms.tasks.Tasks");
                  Method await = Tasks.getMethod("await", Class.forName("com.google.android.gms.tasks.Task"));
                  Object appSetInfo = await.invoke(null, taskWithAppSetInfo);
                  Method getId = appSetInfo.getClass().getMethod("getId");
                  appSetId = (String) getId.invoke(appSetInfo);
              } catch (ClassNotFoundException e) {
                  AmplitudeLog.getLogger().w(TAG, "Google Play Services SDK not found for app set id!");
              } catch (InvocationTargetException e) {
                  AmplitudeLog.getLogger().w(TAG, "Google Play Services not available for app set id");
              } catch (Exception e) {
                  AmplitudeLog.getLogger().e(TAG, "Encountered an error connecting to Google Play Services for app set id", e);
              }
  
              return appSetId;
          }
  
          private String getAndCacheAmazonAdvertisingId() {
              ContentResolver cr = context.getContentResolver();
  
              limitAdTrackingEnabled = Secure.getInt(cr, SETTING_LIMIT_AD_TRACKING, 0) == 1;
              advertisingId = Secure.getString(cr, SETTING_ADVERTISING_ID);
  
              return advertisingId;
          }
  
          private String getAndCacheGoogleAdvertisingId() {
              try {
                  Class AdvertisingIdClient = Class
                          .forName("com.google.android.gms.ads.identifier.AdvertisingIdClient");
                  Method getAdvertisingInfo = AdvertisingIdClient.getMethod("getAdvertisingIdInfo",
                          Context.class);
                  Object advertisingInfo = getAdvertisingInfo.invoke(null, context);
                  Method isLimitAdTrackingEnabled = advertisingInfo.getClass().getMethod(
                          "isLimitAdTrackingEnabled");
                  Boolean limitAdTrackingEnabled = (Boolean) isLimitAdTrackingEnabled
                          .invoke(advertisingInfo);
                  this.limitAdTrackingEnabled =
                          limitAdTrackingEnabled != null && limitAdTrackingEnabled;
                  Method getId = advertisingInfo.getClass().getMethod("getId");
                  advertisingId = (String) getId.invoke(advertisingInfo);
              } catch (ClassNotFoundException e) {
                  AmplitudeLog.getLogger().w(TAG, "Google Play Services SDK not found for advertising id!");
              } catch (InvocationTargetException e) {
                  AmplitudeLog.getLogger().w(TAG, "Google Play Services not available for advertising id");
              } catch (Exception e) {
                  AmplitudeLog.getLogger().e(TAG, "Encountered an error connecting to Google Play Services for advertising id", e);
              }
  
              return advertisingId;
          }
  
          private boolean checkGPSEnabled() {
              // This should not be called on the main thread.
              try {
                  Class GPSUtil = Class
                          .forName("com.google.android.gms.common.GooglePlayServicesUtil");
                  Method getGPSAvailable = GPSUtil.getMethod("isGooglePlayServicesAvailable",
                          Context.class);
                  Integer status = (Integer) getGPSAvailable.invoke(null, context);
                  // status 0 corresponds to com.google.android.gms.common.ConnectionResult.SUCCESS;
                  return status != null && status.intValue() == 0;
              } catch (NoClassDefFoundError e) {
                  AmplitudeLog.getLogger().w(TAG, "Google Play Services Util not found!");
              } catch (ClassNotFoundException e) {
                  AmplitudeLog.getLogger().w(TAG, "Google Play Services Util not found!");
              } catch (NoSuchMethodException e) {
                  AmplitudeLog.getLogger().w(TAG, "Google Play Services not available");
              } catch (InvocationTargetException e) {
                  AmplitudeLog.getLogger().w(TAG, "Google Play Services not available");
              } catch (IllegalAccessException e) {
                  AmplitudeLog.getLogger().w(TAG, "Google Play Services not available");
              } catch (Exception e) {
                  AmplitudeLog.getLogger().w(TAG,
                          "Error when checking for Google Play Services: " + e);
              }
              return false;
          }
      }
  
      public DeviceInfo(Context context, boolean locationListening, boolean shouldTrackAdid) {
          this.context = context;
          this.locationListening = locationListening;
          this.shouldTrackAdid = shouldTrackAdid;
      }
  
      private CachedInfo getCachedInfo() {
          if (cachedInfo == null) {
              cachedInfo = new CachedInfo();
          }
          return cachedInfo;
      }
  
      public void prefetch() {
          getCachedInfo();
      }
  
      public static String generateUUID() {
          return UUID.randomUUID().toString();
      }
  
      public String getVersionName() {
          return getCachedInfo().versionName;
      }
  
      public String getOsName() {
          return getCachedInfo().osName;
      }
  
      public String getOsVersion() {
          return getCachedInfo().osVersion;
      }
  
      public String getBrand() {
          return getCachedInfo().brand;
      }
  
      public String getManufacturer() {
          return getCachedInfo().manufacturer;
      }
  
      public String getModel() {
          return getCachedInfo().model;
      }
  
      public String getCarrier() {
          return getCachedInfo().carrier;
      }
  
      public String getCountry() {
          return getCachedInfo().country;
      }
  
      public String getLanguage() {
          return getCachedInfo().language;
      }
  
      public String getAdvertisingId() {
          return getCachedInfo().advertisingId;
      }
  
      public boolean isLimitAdTrackingEnabled() {
          return getCachedInfo().limitAdTrackingEnabled;
      }
  
      public String getAppSetId() {
          return getCachedInfo().appSetId;
      }
  
      public boolean isGooglePlayServicesEnabled() { return getCachedInfo().gpsEnabled; }
  
      public Location getMostRecentLocation() {
          if (!isLocationListening()) {
              return null;
          }
  
          if (!Utils.checkLocationPermissionAllowed(context)) {
              return null;
          }
  
          LocationManager locationManager = (LocationManager) context
                  .getSystemService(Context.LOCATION_SERVICE);
  
          // Don't crash if the device does not have location services.
          if (locationManager == null) {
              return null;
          }
  
          // It's possible that the location service is running out of process
          // and the remote getProviders call fails. Handle null provider lists.
          List<String> providers = null;
          try {
              providers = locationManager.getProviders(true);
          } catch (SecurityException e) {
              // failed to get providers list
          } catch (Exception e) {
              // other causes
          }
          if (providers == null) {
              return null;
          }
  
          List<Location> locations = new ArrayList<Location>();
          for (String provider : providers) {
              Location location = null;
              try {
                  location = locationManager.getLastKnownLocation(provider);
              } catch (SecurityException e) {
                  AmplitudeLog.getLogger().w(TAG, "Failed to get most recent location");
              } catch (Exception e) {
                  AmplitudeLog.getLogger().w(TAG, "Failed to get most recent location");
              }
              if (location != null) {
                  locations.add(location);
              }
          }
  
          long maximumTimestamp = -1;
          Location bestLocation = null;
          for (Location location : locations) {
              if (location.getTime() > maximumTimestamp) {
                  maximumTimestamp = location.getTime();
                  bestLocation = location;
              }
          }
  
          return bestLocation;
      }
  
      public boolean isLocationListening() {
          return locationListening;
      }
  
      public void setLocationListening(boolean locationListening) {
          this.locationListening = locationListening;
      }
  
      // @VisibleForTesting
      protected Geocoder getGeocoder() {
          return new Geocoder(context, Locale.ENGLISH);
      }
  
  }
  