package com.apigee.sdk.apm.android;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.provider.Settings.Secure;
import android.telephony.TelephonyManager;
import android.util.Log;
import com.apigee.sdk.AppIdentification;
import com.apigee.sdk.apm.android.model.ApigeeApp;
import com.apigee.sdk.apm.android.model.ApigeeMobileAPMConstants;
import com.apigee.sdk.apm.android.model.ApigeeMonitoringSettings;
import com.apigee.sdk.apm.android.model.AppConfigCustomParameter;
import com.apigee.sdk.apm.android.model.AppConfigOverrideFilter;
import com.apigee.sdk.apm.android.model.AppConfigOverrideFilter.FILTER_TYPE;
import com.apigee.sdk.apm.android.model.AppConfigURLRegex;
import com.apigee.sdk.apm.android.model.ClientLog;
import com.apigee.sdk.apm.http.impl.client.cache.CacheConfig;
import com.apigee.sdk.data.client.ApigeeDataClient;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Date;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @y.exclude
*/
public class ApigeeActiveSettings implements ApplicationConfigurationService {
public enum ApigeeActiveConfiguration {
kApigeeDefault,
kApigeeABTesting,
kApigeeDeviceType,
kApigeeDeviceLevel
}
public static final String PROP_CACHE_LAST_MODIFIED_DATE = "WebConfigLastModifiedDate";
public static final String CONFIGURATION_FILE_NAME = "config.json";
protected static final String TAG = ApigeeActiveSettings.class.getSimpleName();
private Context appActivity;
private HttpClient client;
private AppIdentification appIdentification;
private ApigeeDataClient dataClient;
private ApigeeMonitoringClient monitoringClient;
private ApigeeApp apigeeApp;
private ApigeeMonitoringSettings activeSettings;
private ApigeeActiveConfiguration activeConfiguration;
private String activeConfigType;
private String activeConfigName;
private int randomNumber;
private SharedPreferences settings;
private SharedPreferences.Editor editor;
private JacksonMarshallingService marshallingService = new JacksonMarshallingService();
public ApigeeActiveSettings(Context appActivity,AppIdentification appIdentification,ApigeeDataClient dataClient,ApigeeMonitoringClient monitoringClient,HttpClient client) {
this.appActivity = appActivity;
this.client = client;
this.appIdentification = appIdentification;
this.dataClient = dataClient;
this.monitoringClient = monitoringClient;
this.randomNumber = new Random().nextInt(1000);
this.settings = appActivity.getSharedPreferences("AndroidHttpClientWrapper_" + appIdentification.getUniqueIdentifier(), Context.MODE_PRIVATE);
this.editor = this.settings.edit();
this.setValidApplicationConfiguration(new DefaultConfigBuilder().getDefaultCompositeApplicationConfigurationModel());
}
private ApigeeApp getCompositeApplicationConfigurationModelFromInputStream(InputStream is) throws LoadConfigurationException {
try {
String output = inputStreamAsString(is);
Object object = marshallingService.demarshall(output,ApigeeApp.class);
return (ApigeeApp) object;
} catch (RuntimeException exception) {
System.out.println("RuntimeException caught:");
exception.printStackTrace();
throw new LoadConfigurationException("Parsing of configuration failed", exception);
} catch (IOException exception) {
System.out.println("IOException caught:");
exception.printStackTrace();
throw new LoadConfigurationException("Parsing error of configuration", exception);
}
}
public boolean loadLocalApplicationConfiguration() throws LoadConfigurationException {
/*
* Pseudocode :
*
* 1. Check to see if an override configuration file exist
* 2. If does not, check first find out the last time the config file was loaded from server
* 3. Check server to see if last modified date != server date
* 4. If new file exists, copy latest configuration file to local storage
* 5. Read local storage and open file
* 6. Deserialize file and set appropriate ApplicationConfigModel
*/
Log.v(ClientLog.TAG_MONITORING_CLIENT, "Loading configuration from cache location");
String lastModifiedDate = this.getSettingsLastModifiedDate();
if(lastModifiedDate == null) {
return false;
} else {
InputStream is = null;
try {
// Attempts to load from cache
is = this.appActivity.openFileInput(getConfigFileName());
ApigeeApp config = this.getCompositeApplicationConfigurationModelFromInputStream(is);
this.apigeeApp = config;
this.setValidApplicationConfiguration(config);
return true;
} catch (FileNotFoundException e1) {
Log.i(TAG, "Could not load cached configuration file");
cleanCache();
throw new LoadConfigurationException("Error loading configuration file. Two possibilties: 1) There wasn't an older config available 2) File failed to open",e1);
} finally {
if( is != null ) {
try {
is.close();
} catch (IOException ignored) {
}
}
}
}
}
public boolean reloadApplicationConfiguration() throws LoadConfigurationException {
String lastModifiedDate = getSettingsLastModifiedDate();
if(lastModifiedDate == null) {
InputStream inputStream = null;
try {
inputStream = appActivity.openFileInput(getConfigFileName());
ApigeeApp config = getCompositeApplicationConfigurationModelFromInputStream(inputStream);
this.apigeeApp = config;
this.setValidApplicationConfiguration(config);
return true;
} catch (FileNotFoundException e1) {
Log.i(TAG, "Could not load cached configuration file");
cleanCache();
throw new LoadConfigurationException("Error loading configuration file. Two possibilties: 1) There wasn't an older config available 2) File failed to open",e1);
} finally {
if( inputStream != null ) {
try {
inputStream.close();
} catch (IOException ignored) {
}
}
}
} else {
return false;
}
}
public String retrieveConfigFromServer() {
if( this.monitoringClient.isDeviceNetworkConnected() ) {
String urlAsString = monitoringClient.getConfigDownloadURL();
InputStream inputStream = null;
try
{
java.net.URL url = new java.net.URL(urlAsString);
java.net.URLConnection connection = url.openConnection();
if( connection != null ) {
javax.net.ssl.HttpsURLConnection httpsURLConnection = null;
java.net.HttpURLConnection httpURLConnection = null;
if( connection instanceof javax.net.ssl.HttpsURLConnection ) {
httpURLConnection = null;
httpsURLConnection = (javax.net.ssl.HttpsURLConnection) connection;
httpsURLConnection.setRequestMethod("GET");
} else if( connection instanceof java.net.HttpURLConnection ) {
httpsURLConnection = null;
httpURLConnection = (java.net.HttpURLConnection) connection;
httpURLConnection.setRequestMethod("GET");
}
if( httpURLConnection != null ) {
httpURLConnection.connect();
} else {
httpsURLConnection.connect();
}
int responseCode;
if( httpURLConnection != null ) {
responseCode = httpURLConnection.getResponseCode();
} else {
responseCode = httpsURLConnection.getResponseCode();
}
if( responseCode == HttpStatus.SC_OK )
{
if( httpURLConnection != null ) {
inputStream = httpURLConnection.getInputStream();
} else {
inputStream = httpsURLConnection.getInputStream();
}
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
int bytesAvailable = inputStream.available();
if( bytesAvailable < 16 ) {
bytesAvailable = 16;
}
StringBuilder sb = new StringBuilder(bytesAvailable);
String line;
while ((line = reader.readLine()) != null)
{
sb.append(line + '\n');
}
return sb.toString();
} else {
Log.e(ClientLog.TAG_MONITORING_CLIENT,"Error encountered retrieving configuration from server. code=" + responseCode);
}
return null;
} else {
Log.e(ClientLog.TAG_MONITORING_CLIENT,"Unable to open connection to server to retrieve configuration");
return null;
}
} catch(Exception e) {
Log.e(ClientLog.TAG_MONITORING_CLIENT,"Exception encountered retrieving configuration from server. " + e.getLocalizedMessage());
return null;
} finally {
if( inputStream != null ) {
try {
inputStream.close();
} catch(IOException ignored) {
}
}
}
} else {
Log.d(ClientLog.TAG_MONITORING_CLIENT, "Unable to retrieve config from server, device not connected to network");
return null;
}
}
public boolean synchronizeConfig()
{
boolean configSynchronized = false;
String jsonConfig = retrieveConfigFromServer();
if( (jsonConfig != null) && (jsonConfig.length() > 0) ) {
Object object = marshallingService.demarshall(jsonConfig,ApigeeApp.class);
ApigeeApp model = (ApigeeApp) object;
if( model != null ) {
Date serverConfigModifiedDate = model.getLastModifiedDate();
String settingsLastModifiedDate = getSettingsLastModifiedDate();
Date clientConfigModifiedDate = null;
if( (settingsLastModifiedDate != null) && (settingsLastModifiedDate.length() > 0) ) {
long modifiedDateMillis = Long.parseLong(settingsLastModifiedDate);
if( modifiedDateMillis > 0 ) {
clientConfigModifiedDate = new Date(modifiedDateMillis);
}
}
// is configuration from server newer than what we currently have?
if( null == clientConfigModifiedDate ||
serverConfigModifiedDate.after(clientConfigModifiedDate) ) {
Log.d(ClientLog.TAG_MONITORING_CLIENT,"updating our configuration with one from server");
String modifiedTimestampAsString = "" + serverConfigModifiedDate.getTime();
if( saveConfig(jsonConfig,modifiedTimestampAsString) ) {
Log.d(ClientLog.TAG_MONITORING_CLIENT,"saved new configuration to local cache");
configSynchronized = true;
} else {
Log.e(ClientLog.TAG_MONITORING_CLIENT,"error: unable to save configuration to local cache");
}
} else {
// our cached configuration is up to date
Log.d(ClientLog.TAG_MONITORING_CLIENT, "cached configuration is up to date");
}
}
}
return configSynchronized;
}
private boolean saveConfig(String configJson,String lastModifiedDate) {
FileOutputStream fos = null;
boolean savedSuccessfully = false;
try {
fos = appActivity.openFileOutput(
getConfigFileName(),
Context.MODE_PRIVATE);
byte[] jsonBytes = configJson.getBytes();
fos.write(jsonBytes,0,jsonBytes.length);
editor.putString(PROP_CACHE_LAST_MODIFIED_DATE,lastModifiedDate);
editor.commit();
savedSuccessfully = true;
} catch (IOException e) {
cleanCache();
savedSuccessfully = false;
} finally {
if( fos != null ) {
try {
fos.close();
} catch (IOException ignored) {
}
}
}
return savedSuccessfully;
}
private void cleanCache() {
appActivity.deleteFile(getConfigFileName());
editor.putString(PROP_CACHE_LAST_MODIFIED_DATE, "");
}
protected class ConfigurationFileResponse {
public boolean hasNewFile = true;
public boolean failed = false;
public String lastModifiedDate;
public InputStream is;
}
public static String inputStreamAsString(InputStream stream) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(stream));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = br.readLine()) != null) {
sb.append(line + "\n");
}
br.close();
return sb.toString();
}
public void setValidApplicationConfiguration(ApigeeApp config) {
this.apigeeApp = config;
if (matchesDeviceLevelFilter(config)) {
this.activeSettings = config.getDeviceLevelAppConfig();
this.activeConfiguration = ApigeeActiveConfiguration.kApigeeDeviceLevel;
this.activeConfigType = ApigeeMobileAPMConstants.CONFIG_TYPE_DEVICE_LEVEL;
this.activeConfigName = ApigeeMobileAPMConstants.kApigeeActiveConfigNameDeviceLevel;
} else if (matchesDeviceTypeFilter(config)) {
this.activeSettings = config.getDeviceTypeAppConfig();
this.activeConfiguration = ApigeeActiveConfiguration.kApigeeDeviceType;
this.activeConfigType = ApigeeMobileAPMConstants.CONFIG_TYPE_DEVICE_TYPE;
this.activeConfigName = ApigeeMobileAPMConstants.kApigeeActiveConfigNameDeviceType;
} else if (matchesABTestingFilter(config)) {
this.activeSettings = config.getABTestingAppConfig();
this.activeConfiguration = ApigeeActiveConfiguration.kApigeeABTesting;
this.activeConfigType = ApigeeMobileAPMConstants.CONFIG_TYPE_AB;
this.activeConfigName = ApigeeMobileAPMConstants.kApigeeActiveConfigNameABTesting;
} else {
this.activeSettings = config.getDefaultAppConfig();
this.activeConfiguration = ApigeeActiveConfiguration.kApigeeDefault;
this.activeConfigType = ApigeeMobileAPMConstants.CONFIG_TYPE_DEFAULT;
this.activeConfigName = ApigeeMobileAPMConstants.kApigeeActiveConfigNameDefault;
}
}
private boolean matchesDeviceLevelFilter(ApigeeApp config) {
if (config.getDeviceLevelOverrideEnabled()) {
try {
TelephonyManager telephonyManager = (TelephonyManager) appActivity
.getSystemService(Context.TELEPHONY_SERVICE);
String imeiDeviceId = telephonyManager.getDeviceId();
String android_id = Secure.getString(
appActivity.getContentResolver(), Secure.ANDROID_ID);
Log.v(ClientLog.TAG_MONITORING_CLIENT, "Trying to match against device ID / IMEI ID : " + android_id + " / " + imeiDeviceId);
String devicePhoneNumber = telephonyManager.getLine1Number();
if (findRegexMatch(config.getDeviceIdFilters(), imeiDeviceId)
|| findRegexMatch(config.getDeviceIdFilters(), android_id)) {
Log.v(ClientLog.TAG_MONITORING_CLIENT,
"Found device ID or imei id match");
return true;
}
if (findRegexMatch(config.getDeviceNumberFilters(),
devicePhoneNumber)) {
Log.v(ClientLog.TAG_MONITORING_CLIENT,
"Found telephone number match");
return true;
}
}
catch (SecurityException e)
{
Log.w(ClientLog.TAG_MONITORING_CLIENT,"Security caught. AndroidManifest not configured to read phone state permissions : " +
e.getMessage());
return false;
}
}
else
{
Log.v(ClientLog.TAG_MONITORING_CLIENT, "Device Level override not enabled ");
}
Log.v(ClientLog.TAG_MONITORING_CLIENT, "Did not find Device Level Match");
return false;
}
private boolean matchesDeviceTypeFilter(
ApigeeApp config) {
if (config.getDeviceTypeOverrideEnabled()) {
try {
TelephonyManager telephonyManager = (TelephonyManager) appActivity
.getSystemService(Context.TELEPHONY_SERVICE);
ConnectivityManager connectivityManager = (ConnectivityManager) appActivity
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager
.getActiveNetworkInfo();
String deviceModel = Build.MODEL;
String devicePlatform = ApigeeMonitoringClient.getDevicePlatform();
String networkOperator = telephonyManager.getNetworkOperatorName();
String networkType = networkInfo.getTypeName();
if (findRegexMatch(config.getDeviceModelRegexFilters(), deviceModel)) {
Log.v(ClientLog.TAG_MONITORING_CLIENT, "Found device model match");
return true;
}
if (findRegexMatch(config.getDevicePlatformRegexFilters(),
devicePlatform)) {
Log.v(ClientLog.TAG_MONITORING_CLIENT, "Found device platform match");
return true;
}
if (findRegexMatch(config.getNetworkOperatorRegexFilters(),
networkOperator)) {
Log.v(ClientLog.TAG_MONITORING_CLIENT,
"Found network operator match");
return true;
}
if (findRegexMatch(config.getNetworkTypeRegexFilters(), networkType)) {
Log.v(ClientLog.TAG_MONITORING_CLIENT, "Found network type match");
return true;
}
}
catch (SecurityException e)
{
Log.w(ClientLog.TAG_MONITORING_CLIENT,"Security caught. AndroidManifest not configured to read phone state permissions : " +
e.getMessage());
return false;
}
}
return false;
}
private boolean matchesABTestingFilter(
ApigeeApp config) {
if (config.getABTestingOverrideEnabled()
&& (config.getABTestingPercentage() != 0)
&& (randomNumber <= config.getABTestingPercentage())) {
Log.v(ClientLog.TAG_MONITORING_CLIENT,
"A/B Testing Match. B percentage * 10 set at : "
+ config.getABTestingPercentage()
+ ". Random Number : " + randomNumber);
return true;
} else {
Log.v(this.getClass().getSimpleName(),
"No A/B Testing Match. B percentage * 10 set at : "
+ config.getABTestingPercentage()
+ ". Random Number : " + randomNumber);
return false;
}
}
private boolean findRegexMatch(Set<AppConfigOverrideFilter> filters, String target) {
for (AppConfigOverrideFilter filter : filters) {
//Special logic for telephone number filtering
if(filter.getFilterType().equals(FILTER_TYPE.DEVICE_NUMBER))
{
return findTelephoneMatch(filter.getFilterValue(), target);
} else {
String regex = filter.getFilterValue();
Pattern patt = Pattern.compile(regex);
Matcher matcher = patt.matcher(target);
if (matcher.matches()) {
return true;
}
}
}
return false;
}
public boolean findTelephoneMatch(String filter, String target) {
String strippedTelephoneNumber = target.replaceAll( "[^\\d]", "" );
String strippedFilter = filter.replaceAll( "[^\\d]", "" );
String regex = ".*" + strippedFilter;
Pattern patt = Pattern.compile(regex);
Matcher matcher = patt.matcher(strippedTelephoneNumber);
return matcher.matches();
}
@Override
public ApigeeMonitoringSettings getConfigurations() {
return this.activeSettings;
}
@Override
public ApigeeApp getApigeeApp() {
return apigeeApp;
}
public String getAppConfigCustomParameter(String tag, String key) {
String customConfigParameterValue = null;
if (this.activeSettings != null && this.activeSettings.getCustomConfigParameters() != null) {
for (AppConfigCustomParameter param : this.activeSettings.getCustomConfigParameters()) {
if (param.getTag() != null && param.getTag().equals(tag) && param.getParamKey() != null && param.getParamKey().equals(key)) {
customConfigParameterValue = param.getParamValue();
break;
}
}
}
return customConfigParameterValue;
}
public String getConfigFileName() {
return this.monitoringClient.getUniqueIdentifierForApp() + "_" + CONFIGURATION_FILE_NAME;
}
protected String getSettingsLastModifiedDate() {
return settings.getString(PROP_CACHE_LAST_MODIFIED_DATE, null);
}
public String getActiveConfigType() {
return this.activeConfigType;
}
public Long getInstaOpsApplicationId() {
return this.apigeeApp.getInstaOpsApplicationId();
}
public UUID getApplicationUUID() {
return this.apigeeApp.getApplicationUUID();
}
public UUID getOrganizationUUID() {
return this.apigeeApp.getOrganizationUUID();
}
public String getOrgName() {
return this.apigeeApp.getOrgName();
}
public String getAppName() {
return this.apigeeApp.getAppName();
}
public String getFullAppName() {
return this.apigeeApp.getFullAppName();
}
public String getAppOwner() {
return this.apigeeApp.getAppOwner();
}
public Date getAppCreatedDate() {
return this.apigeeApp.getCreatedDate();
}
public Date getAppLastModifiedDate() {
return this.apigeeApp.getLastModifiedDate();
}
public Boolean getMonitoringDisabled() {
return this.apigeeApp.getMonitoringDisabled();
}
public Boolean getDeleted() {
return this.apigeeApp.getDeleted();
}
public String getGoogleId() {
return this.apigeeApp.getGoogleId();
}
public String getAppleId() {
return this.apigeeApp.getAppleId();
}
public String getAppDescription(){
return this.apigeeApp.getDescription();
}
public String getEnvironment() {
return this.apigeeApp.getEnvironment();
}
public String getCustomUploadUrl() {
return this.apigeeApp.getCustomUploadUrl();
}
public Integer getABTestingPercentage() {
return this.apigeeApp.getABTestingPercentage();
}
public Set<AppConfigOverrideFilter> getAppConfigOverrideFilters() {
return this.apigeeApp.getAppConfigOverrideFilters();
}
public Set<AppConfigOverrideFilter> getDeviceNumberFilters() {
return this.apigeeApp.getDeviceNumberFilters();
}
public Set<AppConfigOverrideFilter> getDeviceIdFilters() {
return this.apigeeApp.getDeviceIdFilters();
}
public Set<AppConfigOverrideFilter> getDevicePlatformRegexFilters(){
return this.apigeeApp.getDevicePlatformRegexFilters();
}
public Set<AppConfigOverrideFilter> getNetworkTypeRegexFilters() {
return this.apigeeApp.getNetworkTypeRegexFilters();
}
public Set<AppConfigOverrideFilter> getNetworkOperatorRegexFilters() {
return this.apigeeApp.getNetworkOperatorRegexFilters();
}
public CacheConfig getCacheConfig() {
return this.activeSettings.getCacheConfig();
}
public String getSettingsDescription() {
return this.activeSettings.getDescription();
}
public Set<AppConfigURLRegex> getURLRegex() {
return this.activeSettings.getUrlRegex();
}
public Boolean getNetworkMonitoringEnabled() {
return this.activeSettings.getNetworkMonitoringEnabled();
}
public Integer getLogLevelToMonitor() {
return this.activeSettings.getLogLevelToMonitor();
}
public Boolean getEnableLogMonitoring() {
return this.activeSettings.getEnableLogMonitoring();
}
public Set<AppConfigCustomParameter> getCustomConfigParams() {
return this.activeSettings.getCustomConfigParameters();
}
public Boolean getCachingEnabled() {
return this.activeSettings.getCachingEnabled();
}
public Boolean getMonitorAllUrls() {
return this.activeSettings.getMonitorAllUrls();
}
public Boolean getSessionDataCaptureEnabled() {
return this.activeSettings.getSessionDataCaptureEnabled();
}
public Boolean getBatteryStatusCaptureEnabled() {
return this.activeSettings.getBatteryStatusCaptureEnabled();
}
public Boolean getIMEICaptureEnabled() {
return this.activeSettings.getIMEICaptureEnabled();
}
public Boolean getObfuscateIMEI() {
return this.activeSettings.getObfuscateIMEI();
}
public Boolean getDeviceIdCaptureEnabled() {
return this.activeSettings.getDeviceIdCaptureEnabled();
}
public Boolean getObfuscateDeviceId() {
return this.activeSettings.getObfuscateDeviceId();
}
public Boolean getDeviceModelCaptureEnabled() {
return this.activeSettings.getDeviceModelCaptureEnabled();
}
public Boolean getLocationCaptureEnabled() {
return this.activeSettings.getLocationCaptureEnabled();
}
public Long getLocationCaptureResolution() {
return this.activeSettings.getLocationCaptureResolution();
}
public Boolean getNetworkCarrierCaptureEnabled() {
return this.activeSettings.getNetworkCarrierCaptureEnabled();
}
public Boolean getEnableUploadWhenRoaming() {
return this.activeSettings.getEnableUploadWhenRoaming();
}
public Boolean getEnableUploadWhenMobile() {
return this.activeSettings.getEnableUploadWhenMobile();
}
public Long getAgentUploadIntervalInSeconds() {
return this.activeSettings.getAgentUploadIntervalInSeconds();
}
public Long getAgentUploadInterval() {
return this.activeSettings.getAgentUploadInterval();
}
public Long getSamplingRate() {
return this.activeSettings.getSamplingRate();
}
}