├── .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
--------------------------------------------------------------------------------