package com.samknows.measurement; import java.io.IOException; import java.io.InputStream; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Map; import java.util.Properties; import org.apache.commons.io.IOUtils; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.preference.PreferenceManager; import android.telephony.TelephonyManager; import android.util.Log; import com.samknows.libcore.R; import com.samknows.libcore.SKAndroidLogger; import com.samknows.libcore.SKPorting; import com.samknows.libcore.SKConstants; import com.samknows.measurement.environment.Reachability; import com.samknows.measurement.schedule.ScheduleConfig; import com.samknows.measurement.schedule.ScheduleConfig.LocationType; import com.samknows.measurement.schedule.ScheduleConfig.TestAlarmType; import com.samknows.measurement.statemachine.state.StateEnum; import com.samknows.measurement.util.SKDateFormat; import com.samknows.measurement.util.TimeUtils; public class SK2AppSettings extends SKAppSettings { private static final String TAG = SK2AppSettings.class.getName(); //json fields to be included to each submission public static final String JSON_UNIT_ID = "unit_id"; public static final String JSON_APP_VERSION_CODE = "app_version_code"; public static final String JSON_APP_VERSION_NAME = "app_version_name"; public static final String JSON_SCHEDULE_CONFIG_VERSION = "schedule_config_version"; public static final String JSON_TIMEZONE = "timezone"; public static final String JSON_TIMESTAMP = "timestamp"; public static final String JSON_DATETIME = "datetime"; public static final String JSON_ENTERPRISE_ID = "enterprise_id"; public static final String JSON_SIMOPERATORCODE = "sim_operator_code"; //Used to know if the app needs to collect identifiers public boolean anonymous; //protocol scheme used for comunicating with the dcs //public String protocol_scheme; //submit path used to send the results to the dcs public String submit_path; //download config file path public String download_config_path; //Enterprise id read from the properties file public String enterprise_id; //Show data cap form after installation or upgrade public boolean data_cap_welcome; //collect net usage data public boolean collect_traffic_data; public boolean run_in_roaming = false; //Initialise the AppSettings reading from the properties file located in res/raw private SK2AppSettings(Context c) { super(c); int propertiesId = c.getResources().getIdentifier("properties", "raw", c.getPackageName()); InputStream is = c.getResources().openRawResource(propertiesId); Properties p = new Properties(); try { p.load(is); testStartWindowWakeup = Long.valueOf(p.getProperty(SKConstants.PROP_TEST_START_WINDOW_RTC_WAKEUP)); anonymous = SKApplication.getAppInstance().getAnonymous(); submit_path = p.getProperty(SKConstants.PROP_SUBMIT_PATH); download_config_path = p.getProperty(SKConstants.PROP_DOWNLOAD_CONFIG_PATH); enterprise_id = SKApplication.getAppInstance().getEnterpriseId(); data_cap_welcome = Boolean.parseBoolean(p.getProperty(SKConstants.PROP_DATA_CAP_WELCOME)); collect_traffic_data = Boolean.parseBoolean(p.getProperty(SKConstants.PROP_COLLECT_TRAFFIC_DATA)); String roaming = p.getProperty(SKConstants.PROP_RUN_IN_ROAMING); if(roaming != null){ run_in_roaming = Boolean.parseBoolean(roaming); } } catch (IOException e) { SKPorting.sAssertE(TAG, "failed to load properies!"); } catch(NullPointerException npe){ // This should be seen only when running a mock test. Log.e(this.getClass().getName(), "NullPointerException - make sure this happens only when running a mock test!"); SKPorting.sAssertE(TAG, npe.getMessage()); app_version_code = 0; } finally { IOUtils.closeQuietly(is); } } //to be called when the app starts, via: public static void create(Context c) { try { SKAppSettings.instance = new SK2AppSettings(c); } catch (Resources.NotFoundException e) { SKAndroidLogger.sAssertResourcesNotFoundExceptionNotRobolectric(e); } catch (Exception e) { SKPorting.sAssert(false); } } public static SK2AppSettings getSK2AppSettingsInstance() { return (SK2AppSettings)instance; } public boolean updateConfig(ScheduleConfig newConfig){ // boolean ret = false; // ScheduleConfig savedConfig = CachingStorage.getInstance().loadScheduleConfig(); // if(savedConfig == null ){ // Log.d(TAG, "Saved Config is null"); // ret = true; // }else if(savedConfig.toUpdate(newConfig)){ // Log.d(TAG, "Config versions don't match"); // ret = true; // } // if(getForceDownload()){ // Log.d(TAG, "Force update config"); // ret = true; // } // return ret; return false; } public void saveState(StateEnum state) { saveString(SKConstants.PREF_KEY_STATE, String.valueOf(state)); } public StateEnum getState() { StateEnum ret = StateEnum.NONE; String state = getString(SKConstants.PREF_KEY_STATE); if (state != null) { for(StateEnum s: StateEnum.values()){ if(state.equalsIgnoreCase(String.valueOf(s))){ ret = s; break; } } } return ret; } public void ananlyzeConfig(ScheduleConfig config){ setWakeUpEnabledIfNull(config.testAlamType == TestAlarmType.WAKEUP); setLocationTypeIfNull(config.locationType); // This value is used PURELY to indicate if background processing is enabled, or not, in the schedule. saveLong("number_of_tests_schedueld",config.getNumberOfBackgroundTestGroups()); // The following call stores the latest default rule to run background testing - or not - from the config/schedule data. saveBoolean("background_test",config.getBackgroundTest()); if (config.dataCapDefault >= 0) { saveDataCapFromConfig(config.dataCapDefault); } } // This looks first at user preferences - if the user has set a true/false value, that value is returned. // otherwise, we return the "background_test" value last delivered from the config/schedule. public boolean getIsBackgroundTestingEnabledInUserPreferences() { boolean backgroundTest = false; // TODO - this is the OPPOSITE to what is used in MainService SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(ctx); if(p.contains(SKConstants.PREF_SERVICE_ENABLED)){ // user has saved preference - use their setting backgroundTest = p.getBoolean(SKConstants.PREF_SERVICE_ENABLED, false); }else{ // user has NOT saved preference - use the setting from the config/schedule. backgroundTest = getBoolean("background_test",true); } return backgroundTest; } // Methods for managing the testStartWindow public long getTestStartWindow(){ return isWakeUpEnabled() ? testStartWindowWakeup : testStartWindow; } public void appendUsedBytes(long bytes) { if(Reachability.sGetIsNetworkWiFi()){ return; } resetDataUsageIfTime(); long newBytes = getUsedBytes() + bytes; saveLong(SKConstants.PREF_KEY_USED_BYTES,newBytes); long newTime = System.currentTimeMillis(); saveLong(SKConstants.PREF_KEY_USED_BYTES_LAST_TIME, newTime); Log.d(TAG, "appendUsedBytes=" + bytes + ", saved new as newBytes " + newBytes + " at time "+ newTime); } public long getUsedBytes() { return getLong(SKConstants.PREF_KEY_USED_BYTES, 0); } public void saveDataCapFromConfig(long bytes){ saveLong(SKConstants.PREF_DATA_CAP, bytes); } /** * data cap in bytes * if preference has been defined use it * otherwise use the datacap from config file * if none of them is defined use 1024L*1024L * */ public long getDataCapBytes() { long ret = Long.MAX_VALUE; long configDataCap = getLong(SKConstants.PREF_DATA_CAP,-1); long preferenceDataCap = Long.valueOf(PreferenceManager.getDefaultSharedPreferences(ctx).getString(SKConstants.PREF_DATA_CAP, "-1")); //in megs if(preferenceDataCap>0){ ret = preferenceDataCap * 1024L * 1024L; }else if(configDataCap > 0){ ret = configDataCap * 1024L * 1024L; } return ret; } public void resetDataUsage(){ saveLong(SKConstants.PREF_KEY_USED_BYTES,0); } private void resetDataUsageIfTime(){ Calendar c = GregorianCalendar.getInstance(); long startToday = TimeUtils.getStartDayTime(); long startDayLastTime =TimeUtils.getStartDayTime(getLong(SKConstants.PREF_KEY_USED_BYTES_LAST_TIME, System.currentTimeMillis())); int currDayReset = PreferenceManager.getDefaultSharedPreferences(ctx).getInt(SKConstants.PREF_KEY_DATA_CAP_DAY_IN_MONTH_RESET, 1); long timeReset = TimeUtils.getPreviousDayInMonth(currDayReset); if( startDayLastTime < timeReset && startToday >= timeReset){ Log.d(TAG, "Data usage has been reset to 0 for the month useage. Reset time: "+timeReset+" last time: "+ startDayLastTime+" now: "+startToday); resetDataUsage(); } } public boolean isDataCapAlreadyReached(){ if(Reachability.sGetIsNetworkWiFi()){ return false; } resetDataUsageIfTime(); long usedBytes = getUsedBytes(); long dataCapBytes = getDataCapBytes(); Log.d(TAG, "Currently used bytes "+usedBytes+", DataCap is "+dataCapBytes+", bytes to be used "); if (usedBytes >= dataCapBytes) { if (SKApplication.getAppInstance().getIsDataCapEnabled() == true) { // Datacap Enabled (the default case) return true; } else { // Datacap DISABLED, by user-specific override in Preferences screen. return false; } } return false; } public boolean isDataCapLikelyToBeReached(long bytesToBeUsed){ if(Reachability.sGetIsNetworkWiFi()){ return false; } resetDataUsageIfTime(); long usedBytes = getUsedBytes(); long dataCapBytes = getDataCapBytes(); Log.d(TAG, "Currently used bytes "+usedBytes+", DataCap is "+dataCapBytes+", bytes to be used "+ bytesToBeUsed +"." ); // The value of "bytesToBeUsed" is generally *MUCH* higher than the *actually* used value. // e.g. 40+MB, compared to 4MB. The reason is that the value is from SCHEDULE.xml, and specifies the absolute // maximum that a test is allowed to use; in practise, the test runs for a capped amount of time (also in the schedule data), // and processes far less data that the defined maximum number of bytes to use. if (usedBytes + bytesToBeUsed >= dataCapBytes) { if (SKApplication.getAppInstance().getIsDataCapEnabled() == true) { // Datacap Enabled (the default case) return true; } else { // Datacap DISABLED, by user-specific override in Preferences screen. return false; } } return false; } // public boolean isDataCapReached() { // return isDataCapAlreadyReached(); // } // http://stackoverflow.com/questions/18393175/how-to-properly-check-android-permission-dynamically //for example, permission can be "android.permission.WRITE_EXTERNAL_STORAGE" public static boolean sHasPermission(String permission) { try { Context context = SKApplication.getAppInstance().getApplicationContext(); PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS); if (info.requestedPermissions != null) { for (String p : info.requestedPermissions) { if (p.equals(permission)) { return true; } } } } catch (Exception e) { SKPorting.sAssert(false); } return false; } public LocationType getLocationServiceType() { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx); if (prefs.contains(SKConstants.PREF_LOCATION_TYPE)) { String pref = prefs.getString(SKConstants.PREF_LOCATION_TYPE, null); if(pref == null){ return null; } if (SK2AppSettings.sHasPermission("android.permission.ACCESS_FINE_LOCATION") == false) { // IF app doesn't support GPS, then FORCE use of network provider version! return LocationType.network; } if(pref.equals(ctx.getString(R.string.GPS))) { return LocationType.gps; } return LocationType.network; } return LocationType.gps; } public void setLocationTypeIfNull(LocationType type) { if (!PreferenceManager.getDefaultSharedPreferences(ctx).contains(SKConstants.PREF_LOCATION_TYPE)) { String value = type == LocationType.gps ? ctx.getString(R.string.GPS) : ctx.getString(R.string.MobileNetwork); PreferenceManager.getDefaultSharedPreferences(ctx).edit().putString(SKConstants.PREF_LOCATION_TYPE, value).commit(); } } public String getLocationTypeAsString() { return PreferenceManager.getDefaultSharedPreferences(ctx).getString( SKConstants.PREF_LOCATION_TYPE, ctx.getString(R.string.MobileNetwork)); } //Returns a Map containing all the json entries to be added when submitting the results public Map<String,Object> getJSONExtra(){ Map<String, Object> ret= new HashMap<>(); if(!anonymous && getUnitId() != null){ ret.put(JSON_UNIT_ID, getUnitId()); } ret.put(JSON_APP_VERSION_NAME, app_version_name); ret.put(JSON_APP_VERSION_CODE, app_version_code); ScheduleConfig config = CachingStorage.getInstance().loadScheduleConfig(); if( config !=null){ ret.put(JSON_SCHEDULE_CONFIG_VERSION, config.version ); }else{ ret.put(JSON_SCHEDULE_CONFIG_VERSION, "no_schedule_config" ); } long time = System.currentTimeMillis(); ret.put(JSON_TIMESTAMP, (time/1000)); java.util.Date now = new java.util.Date(time); ret.put(JSON_DATETIME, SKDateFormat.sGetDateAsIso8601String(now)); //ret.put(JSON_TIMEZONE, TimeUtils.millisToHours(TimeZone.getDefault().getRawOffset())); ret.put(JSON_TIMEZONE, String.valueOf(SKDateFormat.sUTCTimezoneAsInteger(now))); if(enterprise_id != null){ ret.put(JSON_ENTERPRISE_ID, enterprise_id); } TelephonyManager manager = (TelephonyManager) ctx.getSystemService(Context.TELEPHONY_SERVICE); String simOperatorCode = ""; if(manager != null){ simOperatorCode = manager.getSimOperator(); } ret.put(JSON_SIMOPERATORCODE, simOperatorCode); return ret; } }