├── .gitignore ├── README.md ├── example ├── AndroidManifest.xml ├── assets │ └── rc.json ├── ic_launcher-web.png ├── libs │ └── remoteConfig_v1.jar ├── proguard-project.txt ├── project.properties ├── remote_rc.json ├── res │ ├── drawable-hdpi │ │ └── ic_launcher.png │ ├── drawable-mdpi │ │ └── ic_launcher.png │ ├── drawable-xhdpi │ │ └── ic_launcher.png │ ├── drawable-xxhdpi │ │ └── ic_launcher.png │ ├── layout │ │ └── activity_main.xml │ └── values │ │ ├── strings.xml │ │ └── styles.xml └── src │ └── is │ └── gangverk │ └── example │ └── remoteconfig │ ├── MainActivity.java │ └── RemoteApplication.java ├── library ├── .classpath ├── .project ├── AndroidManifest.xml ├── libs │ └── android-support-v4.jar ├── proguard-project.txt ├── project.properties └── src │ └── is │ └── gangverk │ └── remoteconfig │ ├── RemoteConfig.java │ └── Utils.java └── remote-config-1.1.jar /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | 15 | # Local configuration file (sdk path, etc) 16 | local.properties 17 | 18 | # Eclipse project files 19 | .classpath 20 | .project 21 | .metadata 22 | .settings -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RemoteConfig 2 | 3 | Android library for loading a remote JSON config file with locally defined default values into the shared preferences. 4 | 5 | ## Installation 6 | Download the jar file and put it into your libs folder. [`Remote Config v1`](https://github.com/gangverk/Android-RemoteConfig/releases/download/1.1/remote-config-1.1.jar) . 7 | 8 | ## Implementation 9 | There are three steps to follow when using RemoteConfig 10 | 11 | ### Step 1 : Add permission to your manifest file 12 | Add the INTERNET and ACCESS_NETWORK_STATE permission to your manifest file. Inside the manifest tag add `` and ` ` 13 | 14 | ### Step 2 : Add configure strings to your strings.xml file 15 | You can add the string "rc_config_location" [optional] and the integer "rc_config_update_interval" [required] into your strings file. These are the url to your config file and the update interval of your preferences in milliseconds respectively. 16 | 17 | ### Step 3 : Initialize the RemoteConfig object 18 | It is highly recommended that you use RemoteConfig as a singleton. To do that you have to override the application class and add android:name=".[MYAPPLICATION]" under the application tag in your manifest. An example of an overridden application class may be found in the example project. [`Application file`](https://github.com/gangverk/Android-RemoteConfig/blob/master/example/src/is/gangverk/example/remoteconfig/RemoteApplication.java) 19 | 20 | ### Listen to changes 21 | There are two ways to listen for changes. One is using the RemoteConfigListener interface and the other is using the LocalBroadcastManager from the support package and registering for it using the registerForBroadcast method. -------------------------------------------------------------------------------- /example/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 12 | 13 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /example/assets/rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "remoteString": "This is remote", 3 | "remoteInt": 3, 4 | "remoteArray": [ 5 | "remoteArray1", 6 | "remoteArray2", 7 | "remoteArray3" 8 | ], 9 | "remoteObject": { 10 | "remoteObject0": "string_0", 11 | "remoteObject1": 1 12 | } 13 | } -------------------------------------------------------------------------------- /example/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsling/Android-RemoteConfig/eb37cba8eba7377f86937d33c67adf77726a25fd/example/ic_launcher-web.png -------------------------------------------------------------------------------- /example/libs/remoteConfig_v1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsling/Android-RemoteConfig/eb37cba8eba7377f86937d33c67adf77726a25fd/example/libs/remoteConfig_v1.jar -------------------------------------------------------------------------------- /example/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /example/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-19 15 | -------------------------------------------------------------------------------- /example/remote_rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "remoteString": "NEW: This is remote", 3 | "remoteInt": 35, 4 | "remoteArray": [ 5 | "NEW: remoteArray1", 6 | "NEW: remoteArray2", 7 | "NEW: remoteArray3" 8 | ], 9 | "remoteObject": { 10 | "remoteObject0": "NEW: string_0", 11 | "remoteObject1": 1 12 | } 13 | } -------------------------------------------------------------------------------- /example/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsling/Android-RemoteConfig/eb37cba8eba7377f86937d33c67adf77726a25fd/example/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsling/Android-RemoteConfig/eb37cba8eba7377f86937d33c67adf77726a25fd/example/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsling/Android-RemoteConfig/eb37cba8eba7377f86937d33c67adf77726a25fd/example/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsling/Android-RemoteConfig/eb37cba8eba7377f86937d33c67adf77726a25fd/example/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | 15 | -------------------------------------------------------------------------------- /example/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | example 5 | 6 | 7 | https://raw.github.com/gangverk/Android-RemoteConfig/master/example/remote_rc.json 8 | 86400000 9 | 10 | -------------------------------------------------------------------------------- /example/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 14 | 15 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /example/src/is/gangverk/example/remoteconfig/MainActivity.java: -------------------------------------------------------------------------------- 1 | package is.gangverk.example.remoteconfig; 2 | 3 | import is.gangverk.remoteconfig.RemoteConfig; 4 | import is.gangverk.remoteconfig.RemoteConfig.RemoteConfigListener; 5 | 6 | import org.json.JSONArray; 7 | import org.json.JSONException; 8 | 9 | import android.app.Activity; 10 | import android.os.Bundle; 11 | import android.widget.TextView; 12 | 13 | public class MainActivity extends Activity implements RemoteConfigListener { 14 | private TextView mStatus; 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.activity_main); 20 | mStatus = (TextView) findViewById(R.id.textview_status); 21 | RemoteConfig.getInstance().addRemoteConfigListener(this); 22 | String remoteString = RemoteConfig.getInstance().getString("remoteString"); 23 | int remoteInt = RemoteConfig.getInstance().getInt("remoteInt"); 24 | String remoteDeepString = RemoteConfig.getInstance().getString("remoteObject.remoteObject0"); 25 | String remoteJsonArray = RemoteConfig.getInstance().getString("remoteArray"); 26 | 27 | try { 28 | // Just to show that the jsonArray is valid, I cast the string to a JSONArray object and back 29 | mStatus.setText(String.format("" + 30 | "Remote string: %s \n" + 31 | "Remote int: %d \n" + 32 | "Remote deep string: %s \n" + 33 | "Remote json array: %s" 34 | ,remoteString, remoteInt, remoteDeepString, new JSONArray(remoteJsonArray).toString())); 35 | } catch (JSONException e) { 36 | e.printStackTrace(); 37 | } 38 | } 39 | 40 | @Override 41 | public void onDownloadComplete(String map) { 42 | mStatus.setText(mStatus.getText() + "\n onDownloadComplete, key=" + map); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /example/src/is/gangverk/example/remoteconfig/RemoteApplication.java: -------------------------------------------------------------------------------- 1 | package is.gangverk.example.remoteconfig; 2 | 3 | import is.gangverk.remoteconfig.RemoteConfig; 4 | import android.app.Application; 5 | 6 | public class RemoteApplication extends Application { 7 | private static final int REMOTE_CONFIG_VERSION = 1; 8 | 9 | @Override 10 | public void onCreate() { 11 | super.onCreate(); 12 | RemoteConfig.getInstance().init(getApplicationContext(), REMOTE_CONFIG_VERSION); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /library/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /library/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | library 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /library/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /library/libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsling/Android-RemoteConfig/eb37cba8eba7377f86937d33c67adf77726a25fd/library/libs/android-support-v4.jar -------------------------------------------------------------------------------- /library/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /library/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-19 15 | android.library=true 16 | -------------------------------------------------------------------------------- /library/src/is/gangverk/remoteconfig/RemoteConfig.java: -------------------------------------------------------------------------------- 1 | package is.gangverk.remoteconfig; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | import java.io.UnsupportedEncodingException; 8 | import java.net.MalformedURLException; 9 | import java.net.URL; 10 | import java.net.URLEncoder; 11 | import java.util.ArrayList; 12 | import java.util.HashMap; 13 | import java.util.Iterator; 14 | 15 | import org.json.JSONArray; 16 | import org.json.JSONException; 17 | import org.json.JSONObject; 18 | 19 | import android.annotation.SuppressLint; 20 | import android.content.BroadcastReceiver; 21 | import android.content.Context; 22 | import android.content.Intent; 23 | import android.content.IntentFilter; 24 | import android.content.SharedPreferences; 25 | import android.content.SharedPreferences.Editor; 26 | import android.os.AsyncTask; 27 | import android.support.v4.content.LocalBroadcastManager; 28 | 29 | public class RemoteConfig { 30 | private static final String LAST_DOWNLOADED_CONFIG_KEY = "lastDownloadedConfig"; 31 | // This is just a dot, since we have regular expression we have to have the backslashes as well 32 | private static final String DEEP_DICTIONARY_SEPARATOR_REGEX = "\\."; 33 | private static final String DEEP_DICTIONARY_SEPARATOR = "."; 34 | private static final String REMOTE_CONFIG_FILE = "rc.json"; 35 | private static final String SP_VERSION_KEY = "rc_version"; 36 | private static final String LOCAL_BROADCAST_INTENT = "remote_config_download_complete"; 37 | private static final String COMPLETE_CONFIG_KEY = "rc_complete_config"; 38 | private URL mConfigLocation; 39 | private long mUpdateTime; 40 | private SharedPreferences mPreferences; 41 | private Context mContext; 42 | private ArrayList mListeners; 43 | private int mVersion; 44 | 45 | public RemoteConfig() {} 46 | 47 | private volatile static RemoteConfig instance; 48 | 49 | /** 50 | * Returns singleton class instance 51 | */ 52 | public static RemoteConfig getInstance() { 53 | if (instance == null) { 54 | synchronized (RemoteConfig.class) { 55 | if (instance == null) { 56 | instance = new RemoteConfig(); 57 | } 58 | } 59 | } 60 | return instance; 61 | } 62 | 63 | /** 64 | * Use this method to initialize the remote config. Using this init method you would have to have the 65 | * string named rc_config_location with the config url as value somewhere in your xml files. 66 | * 67 | * @param context Can be application context 68 | * @param version For version control. If this isn't increased with new key/value pairs won't ever be added 69 | */ 70 | public synchronized void init(Context context, int version, boolean useDefault) { 71 | init(context, version, useDefault, context.getString(context.getResources().getIdentifier("rc_config_location", "string", context.getPackageName()))); 72 | } 73 | 74 | /** 75 | * Use this method to initialize the remote config with a custom config location. 76 | * 77 | * @param context Can be application context 78 | * @param version For version control. If this isn't increased with new key/value pairs won't ever be added 79 | * @param useDefault If true then use the assets/rc.json file as default values 80 | * @param location The location of the remote config 81 | */ 82 | @SuppressLint({"CommitPrefEdits"}) 83 | public synchronized void init(Context context, int version, boolean useDefault, String location) { 84 | mContext = context; 85 | mVersion = version; 86 | setConfigImpl(location); 87 | mUpdateTime = context.getResources().getInteger(context.getResources().getIdentifier("rc_config_update_interval", "integer", context.getPackageName())); 88 | int oldVersion = mPreferences.getInt(SP_VERSION_KEY, -1); 89 | if(version>oldVersion) { 90 | mPreferences.edit().clear().apply(); 91 | if(useDefault) { 92 | initializeConfigFile(); 93 | } 94 | } 95 | checkForUpdate(); // We'll fetch new config on launch 96 | } 97 | 98 | @SuppressLint("NewApi") 99 | private void initializeConfigFile() { 100 | // Start with parsing the assets/rc.json file into JSONObject 101 | JSONObject remoteConfig = initialFileToJsonObject(); 102 | if(remoteConfig!=null) { 103 | jsonObjectIntoPreferences(remoteConfig); 104 | } else { 105 | throw new RuntimeException("Unable to read rc.json file. Are you sure it exists in the assets folder?"); 106 | } 107 | } 108 | 109 | private void setConfigImpl(String location) { 110 | URL locationUrl; 111 | try { 112 | locationUrl = new URL(location); 113 | } catch (MalformedURLException e) { 114 | throw new RuntimeException("Unable to parse config URL"); 115 | } 116 | mConfigLocation = locationUrl; 117 | try { 118 | mPreferences = mContext.getSharedPreferences(URLEncoder.encode(mConfigLocation.toString(), "UTF-8"), Context.MODE_PRIVATE); 119 | } catch (UnsupportedEncodingException e) { 120 | e.printStackTrace(); 121 | } 122 | } 123 | 124 | public void setConfig(String location) { 125 | setConfigImpl(location); 126 | boolean updateNeeded = checkForUpdate(); 127 | if(!updateNeeded) { 128 | if(mListeners!=null && mListeners.size()>0) { 129 | for(RemoteConfigListener listener : mListeners) { 130 | listener.onConfigComplete(); 131 | } 132 | } 133 | } 134 | } 135 | 136 | public JSONObject getConfig() { 137 | String completeConfig = getString(COMPLETE_CONFIG_KEY); 138 | JSONObject completeJSON = null; 139 | try { 140 | if(completeConfig==null) { 141 | return null; 142 | } 143 | completeJSON = new JSONObject(completeConfig); 144 | } catch (JSONException e) { 145 | e.printStackTrace(); 146 | } 147 | return completeJSON; 148 | } 149 | 150 | @SuppressLint("CommitPrefEdits") 151 | private synchronized void jsonObjectIntoPreferences(final JSONObject jsonObject) { 152 | Editor editor = mPreferences.edit(); 153 | editor.putInt(SP_VERSION_KEY, mVersion); 154 | editor.putString(COMPLETE_CONFIG_KEY, jsonObject.toString()); 155 | HashMap changedKeys = new HashMap(); 156 | ArrayList allKeys = getAllKeysFromJSONObject(jsonObject, null); 157 | for(String newKey : allKeys) { 158 | 159 | // If the key is inside an inner JSON dictionary it is defined with 160 | // a dot like dictionary1.dictionary2. That's why we split the string 161 | // here 162 | String[] deepKeys = newKey.split(DEEP_DICTIONARY_SEPARATOR_REGEX); 163 | 164 | JSONObject deepDictionary = jsonObject; 165 | 166 | for(int i=0;i it = changedKeys.keySet().iterator(); 209 | if(mListeners!=null && mListeners.size()>0) { 210 | for(RemoteConfigListener listener : mListeners) { 211 | while (it.hasNext()) { 212 | String key = it.next(); 213 | listener.onValueUpdated(key, changedKeys.get(key)); 214 | } 215 | listener.onConfigComplete(); 216 | } 217 | } 218 | LocalBroadcastManager.getInstance(mContext).sendBroadcast(new Intent(LOCAL_BROADCAST_INTENT)); 219 | } 220 | 221 | public void registerForBroadcast(Context context, BroadcastReceiver receiver) { 222 | LocalBroadcastManager.getInstance(context).registerReceiver(receiver, new IntentFilter(LOCAL_BROADCAST_INTENT)); 223 | } 224 | 225 | private JSONObject initialFileToJsonObject() { 226 | JSONObject remoteConfig = null; 227 | InputStream is = null; 228 | StringBuilder total = new StringBuilder(); 229 | try { 230 | is = mContext.getResources().getAssets().open(REMOTE_CONFIG_FILE); 231 | BufferedReader r = new BufferedReader(new InputStreamReader(is)); 232 | String line; 233 | while ((line = r.readLine()) != null) { 234 | total.append(line); 235 | } 236 | String jsonString = total.toString(); 237 | remoteConfig = new JSONObject(jsonString); 238 | } catch (Exception e) {} finally { 239 | if(is!=null) { 240 | try { 241 | is.close(); 242 | } catch (IOException e) { 243 | e.printStackTrace(); 244 | } 245 | } 246 | } 247 | return remoteConfig; 248 | } 249 | 250 | /** 251 | * Checks if it is time for update based on the updateTime variable. 252 | */ 253 | public boolean checkForUpdate() { 254 | if(RemoteConfig.shouldUpdate(mPreferences, mUpdateTime)) { 255 | // Fetch the config 256 | new FetchConfigAsyncTask().execute(); 257 | return true; 258 | } 259 | return false; 260 | } 261 | 262 | 263 | /** 264 | * Takes in the map parameter and returns the mapping if available. If the mapping is not available it 265 | * returns the default value. If the user has never used this before or there has been a long time since 266 | * last check for updated config, new config will be downloaded. 267 | * 268 | * @param mapping The map parameter to fetch something that should be in the remote config 269 | * @return Returns the mapping for the parameter from the shared defaults 270 | */ 271 | public String getString(String mapping) { 272 | checkForUpdate(); 273 | return mPreferences.getString(mapping, null); 274 | } 275 | 276 | public int getInt(String mapping) { 277 | checkForUpdate(); 278 | return mPreferences.getInt(mapping, -1); 279 | } 280 | 281 | @SuppressLint("NewApi") 282 | private synchronized static boolean shouldUpdate(SharedPreferences preferences, long updateTime) { 283 | long lastDownloadedConfig = preferences.getLong(RemoteConfig.LAST_DOWNLOADED_CONFIG_KEY, 0); 284 | return (lastDownloadedConfig + updateTime < System.currentTimeMillis()); 285 | } 286 | 287 | public interface RemoteConfigListener { 288 | /** 289 | * This method is called when the config has been downloaded and it's values are being put into shared preferences 290 | * 291 | * @param key The key for the new value in shared preferences 292 | * @param value The updated value 293 | */ 294 | public void onValueUpdated(String key, Object value); 295 | 296 | /** 297 | * This is called after every new value has been put into shared preferences 298 | */ 299 | public void onConfigComplete(); 300 | 301 | /** 302 | * In case of error, this is called 303 | * 304 | * @param string The error message 305 | */ 306 | public void onConfigError(String string); 307 | } 308 | 309 | private ArrayList getAllKeysFromJSONObject(JSONObject jsonObject, String prefix) { 310 | ArrayList allKeys = new ArrayList(); 311 | Iterator iter = jsonObject.keys(); 312 | while (iter.hasNext()) { 313 | try { 314 | String key = (String)iter.next(); 315 | Object value = jsonObject.get(key); 316 | String newKey = null; 317 | if(prefix!=null) { 318 | newKey = prefix + DEEP_DICTIONARY_SEPARATOR + key; 319 | } else { 320 | newKey = key; 321 | } 322 | if(value instanceof JSONObject) { 323 | allKeys.addAll(getAllKeysFromJSONObject(((JSONObject)jsonObject.get(key)), newKey)); 324 | } else { 325 | allKeys.add(newKey); 326 | } 327 | } catch (JSONException e) { 328 | e.printStackTrace(); 329 | } 330 | } 331 | return allKeys; 332 | } 333 | 334 | /** 335 | * Adds a listener to the remote config that can react to new values being downloaded 336 | * 337 | * @param listener The listener to listen for new config values 338 | */ 339 | public void addRemoteConfigListener(RemoteConfigListener listener) { 340 | if(mListeners==null) 341 | mListeners = new ArrayList(); 342 | mListeners.add(listener); 343 | } 344 | 345 | /** 346 | * Removes a listener 347 | * 348 | * @param listener The listener to remove 349 | */ 350 | public void removeRemoteConfigListener(RemoteConfigListener listener) { 351 | if(mListeners!=null) { 352 | mListeners.remove(listener); 353 | } 354 | } 355 | 356 | private class FetchConfigAsyncTask extends AsyncTask { 357 | 358 | @Override 359 | protected JSONObject doInBackground(Void... params) { 360 | return Utils.readJSONFeed(mConfigLocation.toString(), null); 361 | } 362 | 363 | @Override 364 | protected void onPostExecute(JSONObject config) { 365 | if(config!=null) { 366 | Editor editor = mPreferences.edit(); 367 | editor.putLong(RemoteConfig.LAST_DOWNLOADED_CONFIG_KEY, System.currentTimeMillis()); 368 | editor.apply(); 369 | jsonObjectIntoPreferences(config); 370 | } else { 371 | if(mListeners!=null) { 372 | for (int i = 0; i < mListeners.size(); i++) { 373 | mListeners.get(i).onConfigError("Unable to read remote config"); 374 | } 375 | } 376 | } 377 | } 378 | } 379 | } 380 | 381 | -------------------------------------------------------------------------------- /library/src/is/gangverk/remoteconfig/Utils.java: -------------------------------------------------------------------------------- 1 | package is.gangverk.remoteconfig; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | import java.net.Socket; 8 | import java.net.URI; 9 | import java.net.UnknownHostException; 10 | import java.security.KeyManagementException; 11 | import java.security.KeyStore; 12 | import java.security.KeyStoreException; 13 | import java.security.NoSuchAlgorithmException; 14 | import java.security.UnrecoverableKeyException; 15 | import java.security.cert.CertificateException; 16 | import java.security.cert.X509Certificate; 17 | import java.util.ArrayList; 18 | 19 | import javax.net.ssl.SSLContext; 20 | import javax.net.ssl.TrustManager; 21 | import javax.net.ssl.X509TrustManager; 22 | 23 | import org.apache.http.HttpEntity; 24 | import org.apache.http.HttpResponse; 25 | import org.apache.http.HttpVersion; 26 | import org.apache.http.StatusLine; 27 | import org.apache.http.client.methods.HttpGet; 28 | import org.apache.http.client.methods.HttpRequestBase; 29 | import org.apache.http.conn.ClientConnectionManager; 30 | import org.apache.http.conn.scheme.PlainSocketFactory; 31 | import org.apache.http.conn.scheme.Scheme; 32 | import org.apache.http.conn.scheme.SchemeRegistry; 33 | import org.apache.http.conn.ssl.SSLSocketFactory; 34 | import org.apache.http.impl.client.DefaultHttpClient; 35 | import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; 36 | import org.apache.http.params.BasicHttpParams; 37 | import org.apache.http.params.HttpConnectionParams; 38 | import org.apache.http.params.HttpParams; 39 | import org.apache.http.params.HttpProtocolParams; 40 | import org.apache.http.protocol.HTTP; 41 | import org.json.JSONException; 42 | import org.json.JSONObject; 43 | 44 | import android.content.Context; 45 | import android.net.ConnectivityManager; 46 | import android.net.NetworkInfo; 47 | import android.os.Build; 48 | import android.util.Pair; 49 | 50 | public class Utils { 51 | 52 | /** 53 | * Checks if there is wifi or mobile connection available 54 | * @param context The application context 55 | * @return true if there is network connection available 56 | */ 57 | public static boolean isNetworkConnection(Context context) { 58 | ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 59 | NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); 60 | return activeNetwork != null && activeNetwork.isConnected(); 61 | } 62 | 63 | /** 64 | * Reads a stream and writes it into a string. Closes inputStream when done. 65 | * @param inputStream The stream to read 66 | * @return A string, containing stream data 67 | * @throws java.io.IOException 68 | */ 69 | public static String stringFromStream(InputStream inputStream) throws java.io.IOException{ 70 | String encoding = "UTF-8"; 71 | StringBuilder builder = new StringBuilder(); 72 | BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, encoding)); 73 | String line; 74 | while((line = reader.readLine()) != null) { 75 | builder.append(line); 76 | } 77 | reader.close(); 78 | return builder.toString(); 79 | } 80 | 81 | private static final int HTTP_CONNECTION_TIMEOUT = 80000; 82 | private static final int HTTP_SOCKET_TIMEOUT = 100000; 83 | 84 | /** 85 | * Get the default http params (to prevent infinite http hangs) 86 | * @return reasonable default for a HttpClient 87 | */ 88 | public static void setHttpTimeoutParams(DefaultHttpClient httpClient) { 89 | HttpParams httpParameters = new BasicHttpParams(); 90 | // connection established timeout 91 | HttpConnectionParams.setConnectionTimeout(httpParameters, HTTP_CONNECTION_TIMEOUT); 92 | // socket timeout 93 | HttpConnectionParams.setSoTimeout(httpParameters, HTTP_SOCKET_TIMEOUT); 94 | httpClient.setParams(httpParameters); 95 | } 96 | 97 | public static String readJSONFeedString(String urlString, ArrayList> headers) { 98 | if(urlString==null) 99 | return null; 100 | String stringResponse = null; 101 | DefaultHttpClient httpClient = getDefaultHttpClient(); 102 | Utils.setHttpTimeoutParams(httpClient); 103 | try { 104 | HttpRequestBase httpRequest = new HttpGet(); 105 | httpRequest.addHeader("Accept", "application/json"); 106 | if(headers != null) { 107 | for(int i = 0; i Build.VERSION_CODES.GINGERBREAD_MR1) { // 10 = Android 2.3.3 133 | return new DefaultHttpClient(); 134 | } 135 | try { 136 | KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); 137 | trustStore.load(null, null); 138 | 139 | SSLSocketFactory sf = new MySSLSocketFactory(trustStore); 140 | sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); 141 | 142 | HttpParams params = new BasicHttpParams(); 143 | HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); 144 | HttpProtocolParams.setContentCharset(params, HTTP.UTF_8); 145 | 146 | SchemeRegistry registry = new SchemeRegistry(); 147 | registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); 148 | registry.register(new Scheme("https", sf, 443)); 149 | 150 | ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry); 151 | 152 | return new DefaultHttpClient(ccm, params); 153 | } catch (Exception e) { 154 | return new DefaultHttpClient(); 155 | } 156 | } 157 | 158 | /** 159 | * Avoid ssl exception on some phones 160 | * see: http://stackoverflow.com/questions/2642777/trusting-all-certificates-using-httpclient-over-https 161 | * 162 | */ 163 | private static class MySSLSocketFactory extends SSLSocketFactory { 164 | SSLContext sslContext = SSLContext.getInstance("TLS"); 165 | 166 | public MySSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { 167 | super(truststore); 168 | 169 | TrustManager tm = new X509TrustManager() { 170 | public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { 171 | } 172 | 173 | public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { 174 | } 175 | 176 | public X509Certificate[] getAcceptedIssuers() { 177 | return null; 178 | } 179 | }; 180 | 181 | sslContext.init(null, new TrustManager[] { tm }, null); 182 | } 183 | 184 | @Override 185 | public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { 186 | return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); 187 | } 188 | 189 | @Override 190 | public Socket createSocket() throws IOException { 191 | return sslContext.getSocketFactory().createSocket(); 192 | } 193 | } 194 | 195 | public static JSONObject readJSONFeed(String urlString, ArrayList> headers) { 196 | try { 197 | String jsonString = readJSONFeedString(urlString, headers); 198 | if(jsonString==null) return null; 199 | return new JSONObject(jsonString); 200 | } catch (JSONException e) { 201 | e.printStackTrace(); 202 | } 203 | return null; 204 | } 205 | 206 | } 207 | -------------------------------------------------------------------------------- /remote-config-1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getsling/Android-RemoteConfig/eb37cba8eba7377f86937d33c67adf77726a25fd/remote-config-1.1.jar --------------------------------------------------------------------------------