Copy package com.areametrics.nosdkandroid;
// AreaMetrics Snippet for Android v1.8
import android.Manifest;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.RemoteException;
import android.provider.Settings;
import android.support.v4.app.ActivityCompat;
import android.util.Log;
import com.google.android.gms.ads.identifier.AdvertisingIdClient;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import org.altbeacon.beacon.Beacon;
import org.altbeacon.beacon.BeaconConsumer;
import org.altbeacon.beacon.BeaconManager;
import org.altbeacon.beacon.BeaconParser;
import org.altbeacon.beacon.RangeNotifier;
import org.altbeacon.beacon.Region;
import org.altbeacon.beacon.logging.LogManager;
import org.altbeacon.beacon.logging.Loggers;
import org.altbeacon.beacon.powersave.BackgroundPowerSaver;
import org.altbeacon.beacon.startup.BootstrapNotifier;
import org.altbeacon.beacon.startup.RegionBootstrap;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.QueryMap;
public enum AreaMetrics implements BootstrapNotifier, BeaconConsumer, Callback<ResponseBody> {
INSTANCE;
private static final String TAG = "AMS";
private static final String SNIPPET_VERSION = "1.8";
private static String AM_SNIPPET_STORE = "AM_SNIPPET_STORE";
private static String AM_BEACON_BATCH = "AM_BEACON_BATCH";
private static String AM_BATCH_LAST_SENT = "AM_BATCH_LAST_SENT";
private String pubID = null;
private Context myContext = null;
private String adId = null;
private String vendorID = null;
private RegionBootstrap regionBootstrap;
private BackgroundPowerSaver backgroundPowerSaver;
private BeaconManager beaconManager;
private Set<String> rangingRegions = new HashSet<>();
private Set<String> amUUIDs = new HashSet<>(Arrays.asList("B9407F30-F5F8-466E-AFF9-25556B577272"));
private Map<String, Long> lastSentBeacons = new HashMap<>();
private boolean currentlySendingBatch = false;
// Network API Declaration
public interface AMAPIService {
@GET("user_init")
Call<ResponseBody> userInit(@QueryMap Map<String, String> params);
@FormUrlEncoded
@POST("beacon_batch")
Call<ResponseBody> beaconBatch(@FieldMap Map<String, String> params);
}
private Retrofit retrofit = new Retrofit.Builder().baseUrl("https://api.areametrics.com/v3/").build();
private AMAPIService api = retrofit.create(AMAPIService.class);
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
String url = call.request().url().toString();
if (url.endsWith("beacon_batch")) {
if (response.isSuccessful()) {
clearStoredBeacons();
updateStoredLastSend();
}
currentlySendingBatch = false;
}
if (BuildConfig.DEBUG) Log.d(TAG, "Response: " + response.message());
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
String url = call.request().url().toString();
if (url.endsWith("beacon_batch")) {
currentlySendingBatch = false;
}
if (BuildConfig.DEBUG) Log.d(TAG, "Failure: " + t.getMessage());
}
// Start Service
public void startService(Application application, String publisherID) {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT) {
if (BuildConfig.DEBUG) Log.d(TAG, "Early return out of startService due to running KitKat");
return;
}
if (application == null || publisherID == null) {
throw new IllegalArgumentException("Called startService without Publisher ID or Application context");
}
if (!publisherID.endsWith("80")) {
throw new IllegalArgumentException("Called startService with invalid Android Publisher ID");
}
pubID = publisherID;
myContext = application;
new FetchAdID().execute();
if (isUserGDPR()) {
return;
}
LogManager.setLogger(Loggers.empty());
LogManager.setVerboseLoggingEnabled(false);
//beacon monitoring
beaconManager = BeaconManager.getInstanceForApplication(myContext);
beaconManager.getBeaconParsers().add(new BeaconParser("ibeacon").setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24")); //ibeacon
beaconManager.setRegionStatePersistenceEnabled(false);
Region region = new Region("AnyBeacon", null, null, null);
regionBootstrap = new RegionBootstrap(this, region);
backgroundPowerSaver = new BackgroundPowerSaver(myContext);
beaconManager.bind(this);
beaconManager.setForegroundScanPeriod(1_100L);
beaconManager.setForegroundBetweenScanPeriod(5_000L);
beaconManager.setBackgroundScanPeriod(8_000L);
beaconManager.setBackgroundBetweenScanPeriod(300_000L);
try {
beaconManager.updateScanPeriods();
} catch (RemoteException e) {
if (BuildConfig.DEBUG) Log.d(TAG, "RemoteException in updateScanPeriods()");
}
sendBeaconBatchIfNeeded();
if (BuildConfig.DEBUG) Log.d(TAG, "AreaMetrics Initialized Successfully!");
}
// AdID Fetch
private class FetchAdID extends AsyncTask<Void, Void, String> {
@Override
protected String doInBackground(Void... params) {
String gid = null;
if (isGooglePlayServicesAvailable(myContext)) {
try {
AdvertisingIdClient.Info adInfo = AdvertisingIdClient.getAdvertisingIdInfo(myContext);
if (adInfo != null && adInfo.getId() != null) {
gid = adInfo.getId();
}
} catch (Exception e) {
if (BuildConfig.DEBUG) Log.d(TAG, "Getting Ad Client Failed: " + e.getMessage());
}
}
return gid;
}
@Override
protected void onPostExecute (String s) {
if (s != null) {
adId = s;
}
sendUserInit();
}
}
public final boolean isGooglePlayServicesAvailable(Context context) {
GoogleApiAvailability availability = GoogleApiAvailability.getInstance();
if (availability != null) {
int resultCode = availability.isGooglePlayServicesAvailable(context);
return resultCode == ConnectionResult.SUCCESS;
} else {
return false;
}
}
// Beacon Monitoring
@Override
public void didEnterRegion(Region region) {
sendBeaconBatchIfNeeded();
if (!rangingRegions.contains(region.getUniqueId())) {
rangingRegions.add(region.getUniqueId());
try {
beaconManager.startRangingBeaconsInRegion(region);
} catch (RemoteException e) {
if (BuildConfig.DEBUG) Log.d(TAG, "startRangingBeaconsInRegion failed due to unbound beacon manager");
}
}
}
@Override
public void didExitRegion(Region region) {
sendBeaconBatchIfNeeded();
try {
beaconManager.stopRangingBeaconsInRegion(region);
} catch (RemoteException e) {
if (BuildConfig.DEBUG) Log.d(TAG, "stopRangingBeaconsInRegion failed due to unbound beacon manager");
}
if (region.getUniqueId() != null) {
rangingRegions.remove(region.getUniqueId());
}
}
@Override
public void didDetermineStateForRegion(int state, Region region) {
sendBeaconBatchIfNeeded();
}
@Override
public void onBeaconServiceConnect() {
beaconManager.addRangeNotifier(new RangeNotifier() {
@Override
public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
for (Beacon beacon : beacons) {
if (beacon.getIdentifiers().size() < 3) {
continue;
}
storeBeaconIfNeeded(beacon);
}
}
});
try {
beaconManager.startRangingBeaconsInRegion(new Region("AnyBeacon", null, null, null));
} catch (RemoteException e) {
if (BuildConfig.DEBUG) Log.d(TAG, "Failed to start ranging beacons");
}
}
public Context getApplicationContext() {
return myContext;
}
public void unbindService(ServiceConnection var1) {
getApplicationContext().unbindService(var1);
}
public boolean bindService(Intent var1, ServiceConnection var2, int var3) {
return getApplicationContext().bindService(var1, var2, var3);
}
private void clearStoredBeacons() {
SharedPreferences prefs = getApplicationContext().getSharedPreferences(AM_SNIPPET_STORE, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putStringSet(AM_BEACON_BATCH, new HashSet<String>());
editor.apply();
}
private void storeBeaconIfNeeded(Beacon beacon) {
String beaconID = String.format(Locale.US, "%s.%s.%s",
beacon.getId1().toString(),
beacon.getId2().toString(),
beacon.getId3().toString());
if (lastSentBeacons.containsKey(beaconID)) {
long lastTime = lastSentBeacons.get(beaconID);
long delay = amUUIDs.contains(beacon.getId1().toString()) ? 5_000 : 5_000;
if (System.currentTimeMillis() - lastTime < delay) {
return; // early return to limit excess
}
}
lastSentBeacons.put(beaconID, System.currentTimeMillis());
try {
JSONObject beaconJSON = new JSONObject();
long epochSeconds = System.currentTimeMillis() / 1000L;
beaconJSON.put("time", epochSeconds);
beaconJSON.put("uuid", beacon.getId1().toString());
beaconJSON.put("major", beacon.getId2().toInt());
beaconJSON.put("minor", beacon.getId3().toInt());
beaconJSON.put("rssi", beacon.getRssi());
beaconJSON.put("tx_power", beacon.getTxPower());
beaconJSON.put("avg_rssi", beacon.getRunningAverageRssi());
beaconJSON.put("btype", beacon.getParserIdentifier());
beaconJSON.put("manufacturer", beacon.getManufacturer());
beaconJSON.put("beacon_accuracy", beacon.getDistance());
beaconJSON.put("address", beacon.getBluetoothAddress());
beaconJSON.put("num_rssi", beacon.getMeasurementCount());
beaconJSON.put("name", beacon.getBluetoothName());
SharedPreferences prefs = getApplicationContext().getSharedPreferences(AM_SNIPPET_STORE, Context.MODE_PRIVATE);
Set<String> batchStringSet = prefs.getStringSet(AM_BEACON_BATCH, new HashSet<String>());
int batchOverflow = batchStringSet.size() - 100;
if (batchOverflow > 0) {
int index = 0;
for (Iterator<String> i = batchStringSet.iterator(); i.hasNext();) {
i.next();
if (index <= batchOverflow) {
i.remove();
} else {
break;
}
index++;
}
}
batchStringSet.add(beaconJSON.toString());
SharedPreferences.Editor editor = prefs.edit();
editor.putStringSet(AM_BEACON_BATCH, batchStringSet);
editor.apply();
} catch (JSONException e) {
if (BuildConfig.DEBUG) Log.d(TAG, "JSONException while storing Beacon in batch!");
}
sendBeaconBatchIfNeeded();
}
private void updateStoredLastSend() {
SharedPreferences prefs = getApplicationContext().getSharedPreferences(AM_SNIPPET_STORE, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putLong(AM_BATCH_LAST_SENT, System.currentTimeMillis());
editor.apply();
}
private void sendBeaconBatchIfNeeded() {
SharedPreferences prefs = getApplicationContext().getSharedPreferences(AM_SNIPPET_STORE, Context.MODE_PRIVATE);
Set<String> batchStringSet = prefs.getStringSet(AM_BEACON_BATCH, new HashSet<String>());
if (batchStringSet.size() > 0) {
long batchLastSend = prefs.getLong(AM_BATCH_LAST_SENT, 0);
if (System.currentTimeMillis() - batchLastSend > 1800_000) {
sendBeaconBatchToServer(batchStringSet);
}
}
}
private void sendBeaconBatchToServer(Set<String> batchStringSet) {
if (currentlySendingBatch) {
return;
}
currentlySendingBatch = true;
Map<String, String> params = new HashMap<>();
params.put("pub_id", pubID);
params.put("os", "android");
params.put("os_ver", Build.VERSION.RELEASE);
params.put("model", Build.MANUFACTURER + " " + Build.MODEL);
params.put("user_locale", Locale.getDefault().getCountry());
params.put("snippet_ver", SNIPPET_VERSION);
if (!isUserGDPR()) {
if (getVendorID() != null) {
params.put("vendor_id", getVendorID());
}
if (adId != null) {
params.put("ad_id", adId);
}
params.put("agent", System.getProperty("http.agent"));
}
try {
JSONArray batchArray = new JSONArray();
for (String jsonStr : batchStringSet) {
batchArray.put(new JSONObject(jsonStr));
}
params.put("batch", batchArray.toString());
Call<ResponseBody> call = api.beaconBatch(params);
call.enqueue(this);
} catch (JSONException e) {
clearStoredBeacons();
currentlySendingBatch = false;
if (BuildConfig.DEBUG) Log.d(TAG, "JSONException while assembling Beacon Batch for sending!");
}
}
private void sendUserInit() {
Map<String, String> params = new HashMap<>();
params.put("pub_id", pubID);
params.put("os", "android");
params.put("os_ver", Build.VERSION.RELEASE);
params.put("model", Build.MANUFACTURER + " " + Build.MODEL);
params.put("locale", Locale.getDefault().getCountry());
params.put("snippet_ver", SNIPPET_VERSION);
if (getVendorID() != null) {
params.put("vendor_id", getVendorID());
}
if (!isUserGDPR() && adId != null) {
params.put("ad_id", adId);
}
params.put("loc_permission", getLocPermission());
Call<ResponseBody> call = api.userInit(params);
call.enqueue(this);
}
boolean isUserGDPR() {
Set<String> countries = getGDPRCountries();
String device = Locale.getDefault().getCountry();
return device != null && countries.contains(device);
}
private Set<String> getGDPRCountries() {
return new HashSet<>(Arrays.asList("AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL", "PL", "PT", "RO", "SK", "SI", "ES", "SE", "GB", "IS", "LI", "NO"));
}
private String getVendorID() {
if (vendorID == null) {
try {
if (myContext != null && myContext.getContentResolver() != null) {
String androidId = Settings.Secure.getString(myContext.getContentResolver(), Settings.Secure.ANDROID_ID);
if (androidId != null) {
vendorID = UUID.nameUUIDFromBytes(androidId.getBytes("utf8")).toString();
}
}
} catch (UnsupportedEncodingException e) {
if (BuildConfig.DEBUG) Log.d(TAG, "VendorID encoding error");
}
}
return vendorID;
}
private String getLocPermission() {
int fine = ActivityCompat.checkSelfPermission(myContext, Manifest.permission.ACCESS_FINE_LOCATION);
int coarse = ActivityCompat.checkSelfPermission(myContext, Manifest.permission.ACCESS_COARSE_LOCATION);
if (fine == PackageManager.PERMISSION_GRANTED || coarse == PackageManager.PERMISSION_GRANTED) {
return "authorized";
} else {
return "denied";
}
}
}