├── .idea ├── .name ├── vcs.xml ├── misc.xml ├── runConfigurations.xml ├── gradle.xml └── codeStyles │ └── Project.xml ├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── values │ │ │ │ ├── colors.xml │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── layout │ │ │ │ └── activity_main.xml │ │ │ └── drawable │ │ │ │ └── ic_launcher_background.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── remoteconfig │ │ │ └── demo │ │ │ └── MainActivity.java │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── remoteconfig │ │ │ └── demo │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── remoteconfig │ │ └── demo │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── library ├── consumer-rules.pro ├── .gitignore ├── src │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ │ └── values │ │ │ │ └── strings.xml │ │ └── java │ │ │ └── com │ │ │ └── remoteconfig │ │ │ └── library │ │ │ ├── RemoteError.java │ │ │ ├── Network.java │ │ │ ├── toolbox │ │ │ ├── Authenticator.java │ │ │ ├── HttpStack.java │ │ │ ├── HttpResponse.java │ │ │ ├── AdaptedHttpStack.java │ │ │ ├── PoolingByteArrayOutputStream.java │ │ │ ├── BaseHttpStack.java │ │ │ ├── AndroidAuthenticator.java │ │ │ ├── ByteArrayPool.java │ │ │ ├── HttpHeaderParser.java │ │ │ ├── HurlStack.java │ │ │ └── BasicNetwork.java │ │ │ ├── ResponseDelivery.java │ │ │ ├── Header.java │ │ │ ├── AuthFailureError.java │ │ │ ├── FetchRemote.java │ │ │ ├── RetryPolicy.java │ │ │ ├── RemoteConfig.java │ │ │ ├── Response.java │ │ │ ├── DefaultRetryPolicy.java │ │ │ ├── Cache.java │ │ │ ├── RemoteParams.java │ │ │ ├── ExecutorDelivery.java │ │ │ ├── VolleyLog.java │ │ │ ├── NetworkResponse.java │ │ │ ├── NetworkDispatcher.java │ │ │ ├── RequestQueue.java │ │ │ └── CacheDispatcher.java │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── remoteconfig │ │ │ └── library │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── remoteconfig │ │ └── library │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── remote-configure-app.gif ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── remote-config.json ├── .gitignore ├── .travis.yml ├── gradle.properties ├── gradlew.bat ├── README.md ├── gradlew └── LICENSE /.idea/.name: -------------------------------------------------------------------------------- 1 | demo -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':library' 2 | rootProject.name='demo' 3 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /remote-configure-app.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gayanvoice/remote-config/HEAD/remote-configure-app.gif -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | library 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gayanvoice/remote-config/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gayanvoice/remote-config/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gayanvoice/remote-config/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gayanvoice/remote-config/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gayanvoice/remote-config/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gayanvoice/remote-config/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gayanvoice/remote-config/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gayanvoice/remote-config/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gayanvoice/remote-config/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gayanvoice/remote-config/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gayanvoice/remote-config/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /remote-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_text":"gayankuruppu/android-remote-config-lib", 3 | "number":100, 4 | "json_object":{"name":"John", "age":30, "city":"New York"}, 5 | "json_array":["John", "Anna", "Peter"], 6 | "boolean":true 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #303F9F 4 | #212B77 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | demo 3 | response 4 | error 5 | finish 6 | 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Oct 01 07:55:13 IST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | 3 | before_install: 4 | - chmod +x gradlew 5 | 6 | script: ./gradlew :build 7 | 8 | android: 9 | components: 10 | # Uncomment the lines below if you want to 11 | # use the latest revision of Android SDK Tools 12 | - platform-tools 13 | - tools 14 | 15 | # The BuildTools version used by your project 16 | - build-tools-29.0.0 17 | 18 | # The SDK version used to compile your project 19 | - android-28 20 | -------------------------------------------------------------------------------- /app/src/test/java/com/remoteconfig/demo/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.remoteconfig.demo; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /library/src/test/java/com/remoteconfig/library/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.remoteconfig.library; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/remoteconfig/demo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.remoteconfig.demo; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.remoteconfig.demo", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/remoteconfig/library/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.remoteconfig.library; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | 25 | assertEquals("com.remoteconfig.library.test", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /library/src/main/java/com/remoteconfig/library/RemoteError.java: -------------------------------------------------------------------------------- 1 | package com.remoteconfig.library; 2 | 3 | public class RemoteError extends Exception { 4 | public final NetworkResponse networkResponse; 5 | private long networkTimeMs; 6 | 7 | public RemoteError() { 8 | networkResponse = null; 9 | } 10 | 11 | public RemoteError(NetworkResponse response) { 12 | networkResponse = response; 13 | } 14 | 15 | public RemoteError(String exceptionMessage) { 16 | super(exceptionMessage); 17 | networkResponse = null; 18 | } 19 | 20 | public RemoteError(String exceptionMessage, Throwable reason) { 21 | super(exceptionMessage, reason); 22 | networkResponse = null; 23 | } 24 | 25 | public RemoteError(Throwable cause) { 26 | super(cause); 27 | networkResponse = null; 28 | } 29 | 30 | /* package */ void setNetworkTimeMs(long networkTimeMs) { 31 | this.networkTimeMs = networkTimeMs; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 28 5 | 6 | defaultConfig { 7 | minSdkVersion 14 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | 12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 13 | consumerProguardFiles 'consumer-rules.pro' 14 | } 15 | 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | 23 | useLibrary 'org.apache.http.legacy' 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(dir: 'libs', include: ['*.jar']) 28 | 29 | implementation 'androidx.appcompat:appcompat:1.1.0' 30 | testImplementation 'junit:junit:4.12' 31 | androidTestImplementation 'androidx.test:runner:1.2.0' 32 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 33 | 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "com.remoteconfig.demo" 7 | minSdkVersion 14 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | 20 | } 21 | 22 | dependencies { 23 | implementation fileTree(dir: 'libs', include: ['*.jar']) 24 | implementation 'androidx.appcompat:appcompat:1.1.0' 25 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 26 | testImplementation 'junit:junit:4.12' 27 | androidTestImplementation 'androidx.test:runner:1.2.0' 28 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 29 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 30 | 31 | implementation project(':library') 32 | } 33 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1024m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | # AndroidX package structure to make it clearer which packages are bundled with the 20 | # Android operating system, and which are packaged with your app's APK 21 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 22 | android.useAndroidX=true 23 | # Automatically convert third-party libraries to use AndroidX 24 | android.enableJetifier=true 25 | 26 | -------------------------------------------------------------------------------- /library/src/main/java/com/remoteconfig/library/Network.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.remoteconfig.library; 18 | 19 | /** An interface for performing requests. */ 20 | public interface Network { 21 | /** 22 | * Performs the specified request. 23 | * 24 | * @param request Request to process 25 | * @return A {@link NetworkResponse} with data and caching metadata; will never be null 26 | * @throws RemoteError on errors 27 | */ 28 | //performRequest 29 | NetworkResponse performRequest(Request request) throws RemoteError; 30 | } 31 | -------------------------------------------------------------------------------- /library/src/main/java/com/remoteconfig/library/toolbox/Authenticator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.remoteconfig.library.toolbox; 18 | 19 | import com.remoteconfig.library.AuthFailureError; 20 | 21 | /** An interface for interacting with auth tokens. */ 22 | public interface Authenticator { 23 | /** 24 | * Synchronously retrieves an auth token. 25 | * 26 | * @throws AuthFailureError If authentication did not succeed 27 | */ 28 | String getAuthToken() throws AuthFailureError; 29 | 30 | /** Invalidates the provided auth token. */ 31 | void invalidateAuthToken(String authToken); 32 | } 33 | -------------------------------------------------------------------------------- /library/src/main/java/com/remoteconfig/library/ResponseDelivery.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.remoteconfig.library; 18 | 19 | public interface ResponseDelivery { 20 | /** Parses a response from the network or cache and delivers it. */ 21 | void postResponse(Request request, Response response); 22 | 23 | /** 24 | * Parses a response from the network or cache and delivers it. The provided Runnable will be 25 | * executed after delivery. 26 | */ 27 | void postResponse(Request request, Response response, Runnable runnable); 28 | 29 | /** Posts an error for the given request. */ 30 | void postError(Request request, RemoteError error); 31 | } 32 | -------------------------------------------------------------------------------- /library/src/main/java/com/remoteconfig/library/Header.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.remoteconfig.library; 17 | 18 | import android.text.TextUtils; 19 | 20 | /** An HTTP header. */ 21 | 22 | public final class Header { 23 | private final String mName; 24 | private final String mValue; 25 | 26 | public Header(String name, String value) { 27 | mName = name; 28 | mValue = value; 29 | } 30 | 31 | public final String getName() { 32 | return mName; 33 | } 34 | 35 | public final String getValue() { 36 | return mValue; 37 | } 38 | 39 | @Override 40 | public boolean equals(Object o) { 41 | if (this == o) return true; 42 | if (o == null || getClass() != o.getClass()) return false; 43 | 44 | Header header = (Header) o; 45 | 46 | return TextUtils.equals(mName, header.mName) && TextUtils.equals(mValue, header.mValue); 47 | } 48 | 49 | @Override 50 | public int hashCode() { 51 | int result = mName.hashCode(); 52 | result = 31 * result + mValue.hashCode(); 53 | return result; 54 | } 55 | 56 | @Override 57 | public String toString() { 58 | return "Header[name=" + mName + ",value=" + mValue + "]"; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /library/src/main/java/com/remoteconfig/library/AuthFailureError.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.remoteconfig.library; 18 | 19 | import android.content.Intent; 20 | 21 | /** Error indicating that there was an authentication failure when performing a Request. */ 22 | @SuppressWarnings("serial") 23 | public class AuthFailureError extends RemoteError { 24 | /** An intent that can be used to resolve this exception. (Brings up the password dialog.) */ 25 | private Intent mResolutionIntent; 26 | 27 | public AuthFailureError() {} 28 | 29 | public AuthFailureError(Intent intent) { 30 | mResolutionIntent = intent; 31 | } 32 | 33 | public AuthFailureError(NetworkResponse response) { 34 | super(response); 35 | } 36 | 37 | public AuthFailureError(String message) { 38 | super(message); 39 | } 40 | 41 | public AuthFailureError(String message, Exception reason) { 42 | super(message, reason); 43 | } 44 | 45 | public Intent getResolutionIntent() { 46 | return mResolutionIntent; 47 | } 48 | 49 | @Override 50 | public String getMessage() { 51 | if (mResolutionIntent != null) { 52 | return "User needs to (re)enter credentials."; 53 | } 54 | return super.getMessage(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /library/src/main/java/com/remoteconfig/library/FetchRemote.java: -------------------------------------------------------------------------------- 1 | package com.remoteconfig.library; 2 | 3 | import android.content.Context; 4 | import com.remoteconfig.library.Network; 5 | import com.remoteconfig.library.RequestQueue; 6 | import com.remoteconfig.library.toolbox.BaseHttpStack; 7 | import com.remoteconfig.library.toolbox.BasicNetwork; 8 | import com.remoteconfig.library.toolbox.DiskBasedCache; 9 | import com.remoteconfig.library.toolbox.HurlStack; 10 | 11 | import java.io.File; 12 | 13 | public class FetchRemote { 14 | 15 | private static final String DEFAULT_CACHE_DIR = "volley"; 16 | 17 | // changed public -> private 18 | private static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) { 19 | BasicNetwork network; 20 | if (stack == null) { 21 | network = new BasicNetwork(new HurlStack()); 22 | } else { 23 | network = new BasicNetwork(stack); 24 | } 25 | return newRequestQueue(context, network); 26 | } 27 | 28 | private static RequestQueue newRequestQueue(Context context, Network network) { 29 | final Context appContext = context.getApplicationContext(); 30 | DiskBasedCache.FileSupplier cacheSupplier = 31 | new DiskBasedCache.FileSupplier() { 32 | private File cacheDir = null; 33 | 34 | @Override 35 | public File get() { 36 | if (cacheDir == null) { 37 | cacheDir = new File(appContext.getCacheDir(), DEFAULT_CACHE_DIR); 38 | } 39 | return cacheDir; 40 | } 41 | }; 42 | RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheSupplier), network); 43 | queue.start(); 44 | return queue; 45 | } 46 | 47 | public static RequestQueue newRequestQueue(Context context) { 48 | return newRequestQueue(context, (BaseHttpStack) null); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /library/src/main/java/com/remoteconfig/library/toolbox/HttpStack.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.remoteconfig.library.toolbox; 18 | 19 | import com.remoteconfig.library.AuthFailureError; 20 | import com.remoteconfig.library.Request; 21 | 22 | import java.io.IOException; 23 | import java.util.Map; 24 | import org.apache.http.HttpResponse; 25 | 26 | /** 27 | * An HTTP stack abstraction. 28 | * 29 | * @deprecated This interface should be avoided as it depends on the deprecated Apache HTTP library. 30 | * Use {@link BaseHttpStack} to avoid this dependency. This class may be removed in a future 31 | * release of FetchRemote. 32 | */ 33 | @SuppressWarnings("DeprecatedIsStillUsed") 34 | @Deprecated 35 | public interface HttpStack { 36 | /** 37 | * Performs an HTTP request with the given parameters. 38 | * 39 | *

A GET request is sent if request.getPostBody() == null. A POST request is sent otherwise, 40 | * and the Content-Type header is set to request.getPostBodyContentType(). 41 | * 42 | * @param request the request to perform 43 | * @param additionalHeaders additional headers to be sent together with {@link 44 | * Request#getHeaders()} 45 | * @return the HTTP response 46 | */ 47 | // performRequest 48 | HttpResponse performRequest(Request request, Map additionalHeaders) 49 | throws IOException, AuthFailureError; 50 | } 51 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /library/src/main/java/com/remoteconfig/library/RetryPolicy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.remoteconfig.library; 18 | 19 | /** 20 | * Retry policy for a request. 21 | * 22 | *

A retry policy can control two parameters: 23 | * 24 | *

33 | * 34 | *

Note that currently, retries triggered by a retry policy are attempted immediately in sequence 35 | * with no delay between them (although the time between tries may increase if the requests are 36 | * timing out and {@link #getCurrentTimeout()} is returning increasing values). 37 | * 38 | *

By default, FetchRemote uses {@link DefaultRetryPolicy}. 39 | */ 40 | public interface RetryPolicy { 41 | 42 | /** Returns the current timeout (used for logging). */ 43 | int getCurrentTimeout(); 44 | 45 | /** Returns the current retry count (used for logging). */ 46 | int getCurrentRetryCount(); 47 | 48 | /** 49 | * Prepares for the next retry by applying a backoff to the timeout. 50 | * 51 | * @param error The error code of the last attempt. 52 | * @throws RemoteError In the event that the retry could not be performed (for example if we ran 53 | * out of attempts), the passed in error is thrown. 54 | */ 55 | void retry(RemoteError error) throws RemoteError; 56 | } 57 | -------------------------------------------------------------------------------- /library/src/main/java/com/remoteconfig/library/RemoteConfig.java: -------------------------------------------------------------------------------- 1 | package com.remoteconfig.library; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import androidx.annotation.GuardedBy; 7 | import androidx.annotation.Nullable; 8 | import com.remoteconfig.library.NetworkResponse; 9 | import com.remoteconfig.library.RemoteParams; 10 | import com.remoteconfig.library.Request; 11 | import com.remoteconfig.library.Response; 12 | import com.remoteconfig.library.Response.ErrorListener; 13 | import com.remoteconfig.library.Response.Listener; 14 | import com.remoteconfig.library.toolbox.HttpHeaderParser; 15 | 16 | import java.io.UnsupportedEncodingException; 17 | 18 | /** A canned request for retrieving the response body at a given URL as a String. */ 19 | 20 | public class RemoteConfig extends Request { 21 | 22 | private final Object mLock = new Object(); 23 | 24 | private Context mContext; 25 | 26 | @Nullable 27 | @GuardedBy("mLock") 28 | private Listener mListener; 29 | 30 | /** 31 | * Creates a new request with the given method. 32 | * 33 | */ 34 | 35 | public RemoteConfig( 36 | Context context, 37 | String url, 38 | @Nullable Listener listener, 39 | @Nullable ErrorListener errorListener) { 40 | super(0, url, errorListener); 41 | mListener = listener; 42 | mContext = context; 43 | } 44 | 45 | 46 | @Override 47 | public void cancel() { 48 | super.cancel(); 49 | synchronized (mLock) { 50 | mListener = null; 51 | } 52 | } 53 | 54 | @Override 55 | protected void deliverResponse(String response) { 56 | Listener listener; 57 | synchronized (mLock) { 58 | listener = mListener; 59 | } 60 | if (listener != null) { 61 | // set values 62 | Log.e("onComplete", response); 63 | RemoteParams remoteParams = new RemoteParams(mContext); 64 | remoteParams.setResponse(response); 65 | listener.onComplete(); 66 | } 67 | 68 | } 69 | 70 | @Override 71 | @SuppressWarnings("DefaultCharset") 72 | protected Response parseNetworkResponse(NetworkResponse response) { 73 | String parsed; 74 | try { 75 | parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); 76 | } catch (UnsupportedEncodingException e) { 77 | parsed = new String(response.data); 78 | } 79 | return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response)); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /library/src/main/java/com/remoteconfig/library/Response.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.remoteconfig.library; 18 | 19 | /** 20 | * Encapsulates a parsed response for delivery. 21 | * 22 | * @param Parsed type of this response 23 | */ 24 | public class Response { 25 | 26 | /** Callback interface for delivering parsed responses. */ 27 | public interface Listener { 28 | /** Called when a response is received. */ 29 | void onComplete(); 30 | } 31 | 32 | /** Callback interface for delivering error responses. */ 33 | public interface ErrorListener { 34 | /** 35 | * Callback method that an error has been occurred with the provided error code and optional 36 | * user-readable message. 37 | */ 38 | void onError(RemoteError error); 39 | } 40 | 41 | /** Returns a successful response containing the parsed result. */ 42 | public static Response success(T result, Cache.Entry cacheEntry) { 43 | return new Response<>(result, cacheEntry); 44 | } 45 | 46 | /** 47 | * Returns a failed response containing the given error code and an optional localized message 48 | * displayed to the user. 49 | */ 50 | public static Response error(RemoteError error) { 51 | return new Response<>(error); 52 | } 53 | 54 | /** Parsed response, or null in the case of error. */ 55 | public final T result; 56 | 57 | /** Cache metadata for this response, or null in the case of error. */ 58 | public final Cache.Entry cacheEntry; 59 | 60 | /** Detailed error information if errorCode != OK. */ 61 | public final RemoteError error; 62 | 63 | /** True if this response was a soft-expired one and a second one MAY be coming. */ 64 | public boolean intermediate = false; 65 | 66 | /** Returns whether this response is considered successful. */ 67 | public boolean isSuccess() { 68 | return error == null; 69 | } 70 | 71 | private Response(T result, Cache.Entry cacheEntry) { 72 | this.result = result; 73 | this.cacheEntry = cacheEntry; 74 | this.error = null; 75 | } 76 | 77 | private Response(RemoteError error) { 78 | this.result = null; 79 | this.cacheEntry = null; 80 | this.error = error; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /library/src/main/java/com/remoteconfig/library/toolbox/HttpResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.remoteconfig.library.toolbox; 17 | 18 | import com.remoteconfig.library.Header; 19 | 20 | import java.io.InputStream; 21 | import java.util.Collections; 22 | import java.util.List; 23 | 24 | /** A response from an HTTP server. */ 25 | public final class HttpResponse { 26 | 27 | private final int mStatusCode; 28 | private final List

mHeaders; 29 | private final int mContentLength; 30 | private final InputStream mContent; 31 | 32 | /** 33 | * Construct a new HttpResponse for an empty response body. 34 | * 35 | * @param statusCode the HTTP status code of the response 36 | * @param headers the response headers 37 | */ 38 | public HttpResponse(int statusCode, List
headers) { 39 | this(statusCode, headers, /* contentLength= */ -1, /* content= */ null); 40 | } 41 | 42 | /** 43 | * Construct a new HttpResponse. 44 | * 45 | * @param statusCode the HTTP status code of the response 46 | * @param headers the response headers 47 | * @param contentLength the length of the response content. Ignored if there is no content. 48 | * @param content an {@link InputStream} of the response content. May be null to indicate that 49 | * the response has no content. 50 | */ 51 | public HttpResponse( 52 | int statusCode, List
headers, int contentLength, InputStream content) { 53 | mStatusCode = statusCode; 54 | mHeaders = headers; 55 | mContentLength = contentLength; 56 | mContent = content; 57 | } 58 | 59 | /** Returns the HTTP status code of the response. */ 60 | public final int getStatusCode() { 61 | return mStatusCode; 62 | } 63 | 64 | /** Returns the response headers. Must not be mutated directly. */ 65 | public final List
getHeaders() { 66 | return Collections.unmodifiableList(mHeaders); 67 | } 68 | 69 | /** Returns the length of the content. Only valid if {@link #getContent} is non-null. */ 70 | public final int getContentLength() { 71 | return mContentLength; 72 | } 73 | 74 | /** 75 | * Returns an {@link InputStream} of the response content. May be null to indicate that the 76 | * response has no content. 77 | */ 78 | public final InputStream getContent() { 79 | return mContent; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /library/src/main/java/com/remoteconfig/library/toolbox/AdaptedHttpStack.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.remoteconfig.library.toolbox; 17 | 18 | import com.remoteconfig.library.AuthFailureError; 19 | import com.remoteconfig.library.Header; 20 | import com.remoteconfig.library.Request; 21 | 22 | import java.io.IOException; 23 | import java.net.SocketTimeoutException; 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | import java.util.Map; 27 | import org.apache.http.conn.ConnectTimeoutException; 28 | 29 | /** 30 | * {@link BaseHttpStack} implementation wrapping a {@link HttpStack}. 31 | * 32 | *

{@link BasicNetwork} uses this if it is provided a {@link HttpStack} at construction time, 33 | * allowing it to have one implementation based atop {@link BaseHttpStack}. 34 | */ 35 | @SuppressWarnings("deprecation") 36 | class AdaptedHttpStack extends BaseHttpStack { 37 | 38 | private final HttpStack mHttpStack; 39 | 40 | AdaptedHttpStack(HttpStack httpStack) { 41 | mHttpStack = httpStack; 42 | } 43 | 44 | @Override 45 | public HttpResponse executeRequest(Request request, Map additionalHeaders) 46 | throws IOException, AuthFailureError { 47 | org.apache.http.HttpResponse apacheResp; 48 | try { 49 | //performRequest 50 | apacheResp = mHttpStack.performRequest(request, additionalHeaders); 51 | } catch (ConnectTimeoutException e) { 52 | // BasicNetwork won't know that this exception should be retried like a timeout, since 53 | // it's an Apache-specific error, so wrap it in a standard timeout exception. 54 | throw new SocketTimeoutException(e.getMessage()); 55 | } 56 | 57 | int statusCode = apacheResp.getStatusLine().getStatusCode(); 58 | 59 | org.apache.http.Header[] headers = apacheResp.getAllHeaders(); 60 | List

headerList = new ArrayList<>(headers.length); 61 | for (org.apache.http.Header header : headers) { 62 | headerList.add(new Header(header.getName(), header.getValue())); 63 | } 64 | 65 | if (apacheResp.getEntity() == null) { 66 | return new HttpResponse(statusCode, headerList); 67 | } 68 | 69 | long contentLength = apacheResp.getEntity().getContentLength(); 70 | if ((int) contentLength != contentLength) { 71 | throw new IOException("Response too large: " + contentLength); 72 | } 73 | 74 | return new HttpResponse( 75 | statusCode, 76 | headerList, 77 | (int) apacheResp.getEntity().getContentLength(), 78 | apacheResp.getEntity().getContent()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /library/src/main/java/com/remoteconfig/library/toolbox/PoolingByteArrayOutputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.remoteconfig.library.toolbox; 18 | 19 | import java.io.ByteArrayOutputStream; 20 | import java.io.IOException; 21 | 22 | /** 23 | * A variation of {@link ByteArrayOutputStream} that uses a pool of byte[] buffers instead 24 | * of always allocating them fresh, saving on heap churn. 25 | */ 26 | public class PoolingByteArrayOutputStream extends ByteArrayOutputStream { 27 | /** 28 | * If the {@link #PoolingByteArrayOutputStream(ByteArrayPool)} constructor is called, this is 29 | * the default size to which the underlying byte array is initialized. 30 | */ 31 | private static final int DEFAULT_SIZE = 256; 32 | 33 | private final ByteArrayPool mPool; 34 | 35 | /** 36 | * Constructs a new PoolingByteArrayOutputStream with a default size. If more bytes are written 37 | * to this instance, the underlying byte array will expand. 38 | */ 39 | public PoolingByteArrayOutputStream(ByteArrayPool pool) { 40 | this(pool, DEFAULT_SIZE); 41 | } 42 | 43 | /** 44 | * Constructs a new {@code ByteArrayOutputStream} with a default size of {@code size} bytes. If 45 | * more than {@code size} bytes are written to this instance, the underlying byte array will 46 | * expand. 47 | * 48 | * @param size initial size for the underlying byte array. The value will be pinned to a default 49 | * minimum size. 50 | */ 51 | public PoolingByteArrayOutputStream(ByteArrayPool pool, int size) { 52 | mPool = pool; 53 | buf = mPool.getBuf(Math.max(size, DEFAULT_SIZE)); 54 | } 55 | 56 | @Override 57 | public void close() throws IOException { 58 | mPool.returnBuf(buf); 59 | buf = null; 60 | super.close(); 61 | } 62 | 63 | @Override 64 | public void finalize() { 65 | mPool.returnBuf(buf); 66 | } 67 | 68 | /** Ensures there is enough space in the buffer for the given number of additional bytes. */ 69 | @SuppressWarnings("UnsafeFinalization") 70 | private void expand(int i) { 71 | /* Can the buffer handle @i more bytes, if not expand it */ 72 | if (count + i <= buf.length) { 73 | return; 74 | } 75 | byte[] newbuf = mPool.getBuf((count + i) * 2); 76 | System.arraycopy(buf, 0, newbuf, 0, count); 77 | mPool.returnBuf(buf); 78 | buf = newbuf; 79 | } 80 | 81 | @Override 82 | public synchronized void write(byte[] buffer, int offset, int len) { 83 | expand(len); 84 | super.write(buffer, offset, len); 85 | } 86 | 87 | @Override 88 | public synchronized void write(int oneByte) { 89 | expand(1); 90 | super.write(oneByte); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /library/src/main/java/com/remoteconfig/library/DefaultRetryPolicy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.remoteconfig.library; 18 | 19 | /** Default retry policy for requests. */ 20 | public class DefaultRetryPolicy implements RetryPolicy { 21 | /** The current timeout in milliseconds. */ 22 | private int mCurrentTimeoutMs; 23 | 24 | /** The current retry count. */ 25 | private int mCurrentRetryCount; 26 | 27 | /** The maximum number of attempts. */ 28 | private final int mMaxNumRetries; 29 | 30 | /** The backoff multiplier for the policy. */ 31 | private final float mBackoffMultiplier; 32 | 33 | /** The default socket timeout in milliseconds */ 34 | public static final int DEFAULT_TIMEOUT_MS = 2500; 35 | 36 | /** The default number of retries */ 37 | public static final int DEFAULT_MAX_RETRIES = 1; 38 | 39 | /** The default backoff multiplier */ 40 | public static final float DEFAULT_BACKOFF_MULT = 1f; 41 | 42 | /** Constructs a new retry policy using the default timeouts. */ 43 | public DefaultRetryPolicy() { 44 | this(DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRIES, DEFAULT_BACKOFF_MULT); 45 | } 46 | 47 | /** 48 | * Constructs a new retry policy. 49 | * 50 | * @param initialTimeoutMs The initial timeout for the policy. 51 | * @param maxNumRetries The maximum number of retries. 52 | * @param backoffMultiplier Backoff multiplier for the policy. 53 | */ 54 | public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) { 55 | mCurrentTimeoutMs = initialTimeoutMs; 56 | mMaxNumRetries = maxNumRetries; 57 | mBackoffMultiplier = backoffMultiplier; 58 | } 59 | 60 | /** Returns the current timeout. */ 61 | @Override 62 | public int getCurrentTimeout() { 63 | return mCurrentTimeoutMs; 64 | } 65 | 66 | /** Returns the current retry count. */ 67 | @Override 68 | public int getCurrentRetryCount() { 69 | return mCurrentRetryCount; 70 | } 71 | 72 | /** Returns the backoff multiplier for the policy. */ 73 | public float getBackoffMultiplier() { 74 | return mBackoffMultiplier; 75 | } 76 | 77 | /** 78 | * Prepares for the next retry by applying a backoff to the timeout. 79 | * 80 | * @param error The error code of the last attempt. 81 | */ 82 | @Override 83 | public void retry(RemoteError error) throws RemoteError { 84 | mCurrentRetryCount++; 85 | mCurrentTimeoutMs += (int) (mCurrentTimeoutMs * mBackoffMultiplier); 86 | if (!hasAttemptRemaining()) { 87 | throw error; 88 | } 89 | } 90 | 91 | /** Returns true if this policy has attempts remaining, false otherwise. */ 92 | protected boolean hasAttemptRemaining() { 93 | return mCurrentRetryCount <= mMaxNumRetries; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /app/src/main/java/com/remoteconfig/demo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.remoteconfig.demo; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | 5 | import android.os.Bundle; 6 | import android.view.View; 7 | import android.widget.Button; 8 | import android.widget.TextView; 9 | 10 | 11 | import com.remoteconfig.library.*; 12 | 13 | import org.json.JSONArray; 14 | import org.json.JSONObject; 15 | 16 | public class MainActivity extends AppCompatActivity { 17 | 18 | 19 | TextView textViewSimpleText, textViewNumber, textViewJSONObject,textViewJSONArray, textViewBoolean; 20 | Button buttonRequest; 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | setContentView(R.layout.activity_main); 26 | 27 | textViewSimpleText = findViewById(R.id.textViewSimpleText); 28 | textViewNumber = findViewById(R.id.textViewNumber); 29 | textViewJSONObject = findViewById(R.id.textViewJSONObject); 30 | textViewJSONArray = findViewById(R.id.textViewJSONArray); 31 | textViewBoolean = findViewById(R.id.textViewBoolean); 32 | 33 | buttonRequest = findViewById(R.id.buttonRequest); 34 | 35 | 36 | buttonRequest.setOnClickListener(new View.OnClickListener() { 37 | @Override 38 | public void onClick(View view) { 39 | 40 | // set request 41 | RequestQueue queue = FetchRemote.newRequestQueue(MainActivity.this); 42 | 43 | // url of the json file 44 | String mUrl ="https://raw.githubusercontent.com/gayankuruppu/android-remote-config-library/master/remote-config.json"; 45 | 46 | // request the json file 47 | RemoteConfig remoteConfig = new RemoteConfig(MainActivity.this, mUrl, 48 | new Response.Listener() { 49 | @Override 50 | public void onComplete() { 51 | // json file retrieved 52 | RemoteParams remoteParams = new RemoteParams(MainActivity.this); 53 | textViewSimpleText.setText(remoteParams.getString("short_text", "default_text")); 54 | 55 | int intValue = remoteParams.getInt("number", 200); 56 | textViewNumber.setText(String.valueOf(intValue)); 57 | 58 | JSONObject jsonObject = remoteParams.getJSONObject("json_object"); 59 | textViewJSONObject.setText(String.valueOf(jsonObject)); 60 | 61 | JSONArray jsonArray = remoteParams.getJSONArray("json_array"); 62 | textViewJSONArray.setText(String.valueOf(jsonArray)); 63 | 64 | boolean booleanValue = remoteParams.getBoolean("boolean", false); 65 | textViewBoolean.setText(String.valueOf(booleanValue)); 66 | 67 | } 68 | }, 69 | new Response.ErrorListener() { 70 | @Override 71 | public void onError(RemoteError error) { 72 | // json file retrieve error 73 | 74 | } 75 | } 76 | ); 77 | 78 | // clear cache 79 | remoteConfig.setShouldCache(false); 80 | queue.add(remoteConfig); 81 | 82 | } 83 | }); 84 | 85 | 86 | 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /library/src/main/java/com/remoteconfig/library/Cache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.remoteconfig.library; 18 | 19 | import java.util.Collections; 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | /** An interface for a cache keyed by a String with a byte array as data. */ 24 | public interface Cache { 25 | /** 26 | * Retrieves an entry from the cache. 27 | * 28 | * @param key Cache key 29 | * @return An {@link Entry} or null in the event of a cache miss 30 | */ 31 | Entry get(String key); 32 | 33 | /** 34 | * Adds or replaces an entry to the cache. 35 | * 36 | * @param key Cache key 37 | * @param entry Data to store and metadata for cache coherency, TTL, etc. 38 | */ 39 | void put(String key, Entry entry); 40 | 41 | /** 42 | * Performs any potentially long-running actions needed to initialize the cache; will be called 43 | * from a worker thread. 44 | */ 45 | void initialize(); 46 | 47 | /** 48 | * Invalidates an entry in the cache. 49 | * 50 | * @param key Cache key 51 | * @param fullExpire True to fully expire the entry, false to soft expire 52 | */ 53 | void invalidate(String key, boolean fullExpire); 54 | 55 | /** 56 | * Removes an entry from the cache. 57 | * 58 | * @param key Cache key 59 | */ 60 | void remove(String key); 61 | 62 | /** Empties the cache. */ 63 | void clear(); 64 | 65 | /** Data and metadata for an entry returned by the cache. */ 66 | class Entry { 67 | /** The data returned from cache. */ 68 | public byte[] data; 69 | 70 | /** ETag for cache coherency. */ 71 | public String etag; 72 | 73 | /** Date of this response as reported by the server. */ 74 | public long serverDate; 75 | 76 | /** The last modified date for the requested object. */ 77 | public long lastModified; 78 | 79 | /** TTL for this record. */ 80 | public long ttl; 81 | 82 | /** Soft TTL for this record. */ 83 | public long softTtl; 84 | 85 | /** 86 | * Response headers as received from server; must be non-null. Should not be mutated 87 | * directly. 88 | * 89 | *

Note that if the server returns two headers with the same (case-insensitive) name, 90 | * this map will only contain the one of them. {@link #allResponseHeaders} may contain all 91 | * headers if the {@link Cache} implementation supports it. 92 | */ 93 | public Map responseHeaders = Collections.emptyMap(); 94 | 95 | /** 96 | * All response headers. May be null depending on the {@link Cache} implementation. Should 97 | * not be mutated directly. 98 | */ 99 | public List

allResponseHeaders; 100 | 101 | /** True if the entry is expired. */ 102 | public boolean isExpired() { 103 | return this.ttl < System.currentTimeMillis(); 104 | } 105 | 106 | /** True if a refresh is needed from the original data source. */ 107 | public boolean refreshNeeded() { 108 | return this.softTtl < System.currentTimeMillis(); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /library/src/main/java/com/remoteconfig/library/RemoteParams.java: -------------------------------------------------------------------------------- 1 | package com.remoteconfig.library; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.preference.PreferenceManager; 6 | 7 | import org.json.JSONArray; 8 | import org.json.JSONException; 9 | import org.json.JSONObject; 10 | 11 | public class RemoteParams { 12 | 13 | private Context context; 14 | String PREF = "remote_params"; 15 | String DEFAULT_VALUE = "defaultValue"; 16 | 17 | public RemoteParams(Context context){ 18 | this.context = context; 19 | } 20 | 21 | public void setResponse(String response){ 22 | 23 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 24 | SharedPreferences.Editor editor = prefs.edit(); 25 | editor.putString(PREF, response); 26 | editor.apply(); 27 | } 28 | 29 | public String getString(String param, String defaultValue){ 30 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 31 | String jsonPref = prefs.getString(PREF, DEFAULT_VALUE); 32 | try { 33 | JSONObject jsonResponse = new JSONObject(jsonPref); 34 | return jsonResponse.getString(param); 35 | } catch (JSONException e) { 36 | return defaultValue; 37 | } 38 | } 39 | 40 | public int getInt(String param, int defaultValue){ 41 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 42 | String jsonPref = prefs.getString(PREF, DEFAULT_VALUE); 43 | try { 44 | JSONObject jsonResponse = new JSONObject(jsonPref); 45 | return jsonResponse.getInt(param); 46 | } catch (JSONException e) { 47 | return defaultValue; 48 | } 49 | } 50 | 51 | public JSONObject getJSONObject(String param){ 52 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 53 | String jsonPref = prefs.getString(PREF, DEFAULT_VALUE); 54 | try { 55 | JSONObject jsonResponse = new JSONObject(jsonPref); 56 | return jsonResponse.getJSONObject(param); 57 | } catch (JSONException e) { 58 | return null; 59 | } 60 | } 61 | 62 | public JSONObject getJSONObject(String param, JSONObject jsonObject){ 63 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 64 | String jsonPref = prefs.getString(PREF, DEFAULT_VALUE); 65 | try { 66 | JSONObject jsonResponse = new JSONObject(jsonPref); 67 | return jsonResponse.getJSONObject(param); 68 | } catch (JSONException e) { 69 | return jsonObject; 70 | } 71 | } 72 | 73 | public JSONArray getJSONArray(String param){ 74 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 75 | String jsonPref = prefs.getString(PREF, DEFAULT_VALUE); 76 | try { 77 | JSONObject jsonResponse = new JSONObject(jsonPref); 78 | return jsonResponse.getJSONArray(param); 79 | } catch (JSONException e) { 80 | return null; 81 | } 82 | } 83 | 84 | public JSONArray getJSONArray(String param, JSONArray jsonArray){ 85 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 86 | String jsonPref = prefs.getString(PREF, DEFAULT_VALUE); 87 | try { 88 | JSONObject jsonResponse = new JSONObject(jsonPref); 89 | return jsonResponse.getJSONArray(param); 90 | } catch (JSONException e) { 91 | return jsonArray; 92 | } 93 | } 94 | 95 | public boolean getBoolean(String param, boolean defaultValue){ 96 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 97 | String jsonPref = prefs.getString(PREF, DEFAULT_VALUE); 98 | try { 99 | JSONObject jsonResponse = new JSONObject(jsonPref); 100 | return jsonResponse.getBoolean(param); 101 | } catch (JSONException e) { 102 | return defaultValue; 103 | } 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /library/src/main/java/com/remoteconfig/library/toolbox/BaseHttpStack.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2017 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.remoteconfig.library.toolbox; 17 | 18 | import com.remoteconfig.library.AuthFailureError; 19 | import com.remoteconfig.library.Header; 20 | import com.remoteconfig.library.Request; 21 | 22 | import java.io.IOException; 23 | import java.io.InputStream; 24 | import java.net.SocketTimeoutException; 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | import java.util.Map; 28 | import org.apache.http.ProtocolVersion; 29 | import org.apache.http.StatusLine; 30 | import org.apache.http.entity.BasicHttpEntity; 31 | import org.apache.http.message.BasicHeader; 32 | import org.apache.http.message.BasicHttpResponse; 33 | import org.apache.http.message.BasicStatusLine; 34 | 35 | /** An HTTP stack abstraction. */ 36 | @SuppressWarnings("deprecation") // for HttpStack 37 | public abstract class BaseHttpStack implements HttpStack { 38 | 39 | /** 40 | * Performs an HTTP request with the given parameters. 41 | * 42 | *

A GET request is sent if request.getPostBody() == null. A POST request is sent otherwise, 43 | * and the Content-Type header is set to request.getPostBodyContentType(). 44 | * 45 | * @param request the request to perform 46 | * @param additionalHeaders additional headers to be sent together with {@link 47 | * Request#getHeaders()} 48 | * @return the {@link HttpResponse} 49 | * @throws SocketTimeoutException if the request times out 50 | * @throws IOException if another I/O error occurs during the request 51 | * @throws AuthFailureError if an authentication failure occurs during the request 52 | */ 53 | public abstract HttpResponse executeRequest( 54 | Request request, Map additionalHeaders) 55 | throws IOException, AuthFailureError; 56 | 57 | /** 58 | * @deprecated use {@link #executeRequest} instead to avoid a dependency on the deprecated 59 | * Apache HTTP library. Nothing in FetchRemote's own source calls this method. However, since 60 | * {@link BasicNetwork#mHttpStack} is exposed to subclasses, we provide this implementation 61 | * in case legacy client apps are dependent on that field. This method may be removed in a 62 | * future release of FetchRemote. 63 | */ 64 | @Deprecated 65 | @Override 66 | public final org.apache.http.HttpResponse performRequest( 67 | Request request, Map additionalHeaders) 68 | throws IOException, AuthFailureError { 69 | HttpResponse response = executeRequest(request, additionalHeaders); 70 | 71 | ProtocolVersion protocolVersion = new ProtocolVersion("HTTP", 1, 1); 72 | StatusLine statusLine = 73 | new BasicStatusLine( 74 | protocolVersion, response.getStatusCode(), /* reasonPhrase= */ ""); 75 | BasicHttpResponse apacheResponse = new BasicHttpResponse(statusLine); 76 | 77 | List headers = new ArrayList<>(); 78 | for (Header header : response.getHeaders()) { 79 | headers.add(new BasicHeader(header.getName(), header.getValue())); 80 | } 81 | apacheResponse.setHeaders(headers.toArray(new org.apache.http.Header[0])); 82 | 83 | InputStream responseStream = response.getContent(); 84 | if (responseStream != null) { 85 | BasicHttpEntity entity = new BasicHttpEntity(); 86 | entity.setContent(responseStream); 87 | entity.setContentLength(response.getContentLength()); 88 | apacheResponse.setEntity(entity); 89 | } 90 | 91 | return apacheResponse; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /library/src/main/java/com/remoteconfig/library/toolbox/AndroidAuthenticator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.remoteconfig.library.toolbox; 18 | 19 | import android.accounts.Account; 20 | import android.accounts.AccountManager; 21 | import android.accounts.AccountManagerFuture; 22 | import android.annotation.SuppressLint; 23 | import android.content.Context; 24 | import android.content.Intent; 25 | import android.os.Bundle; 26 | import androidx.annotation.VisibleForTesting; 27 | import com.remoteconfig.library.AuthFailureError; 28 | 29 | /** 30 | * An Authenticator that uses {@link AccountManager} to get auth tokens of a specified type for a 31 | * specified account. 32 | */ 33 | // TODO: Update this to account for runtime permissions 34 | @SuppressLint("MissingPermission") 35 | public class AndroidAuthenticator implements Authenticator { 36 | private final AccountManager mAccountManager; 37 | private final Account mAccount; 38 | private final String mAuthTokenType; 39 | private final boolean mNotifyAuthFailure; 40 | 41 | /** 42 | * Creates a new authenticator. 43 | * 44 | * @param context Context for accessing AccountManager 45 | * @param account Account to authenticate as 46 | * @param authTokenType Auth token type passed to AccountManager 47 | */ 48 | public AndroidAuthenticator(Context context, Account account, String authTokenType) { 49 | this(context, account, authTokenType, /* notifyAuthFailure= */ false); 50 | } 51 | 52 | /** 53 | * Creates a new authenticator. 54 | * 55 | * @param context Context for accessing AccountManager 56 | * @param account Account to authenticate as 57 | * @param authTokenType Auth token type passed to AccountManager 58 | * @param notifyAuthFailure Whether to raise a notification upon auth failure 59 | */ 60 | public AndroidAuthenticator( 61 | Context context, Account account, String authTokenType, boolean notifyAuthFailure) { 62 | this(AccountManager.get(context), account, authTokenType, notifyAuthFailure); 63 | } 64 | 65 | @VisibleForTesting 66 | AndroidAuthenticator( 67 | AccountManager accountManager, 68 | Account account, 69 | String authTokenType, 70 | boolean notifyAuthFailure) { 71 | mAccountManager = accountManager; 72 | mAccount = account; 73 | mAuthTokenType = authTokenType; 74 | mNotifyAuthFailure = notifyAuthFailure; 75 | } 76 | 77 | /** Returns the Account being used by this authenticator. */ 78 | public Account getAccount() { 79 | return mAccount; 80 | } 81 | 82 | /** Returns the Auth Token Type used by this authenticator. */ 83 | public String getAuthTokenType() { 84 | return mAuthTokenType; 85 | } 86 | 87 | // TODO: Figure out what to do about notifyAuthFailure 88 | @SuppressWarnings("deprecation") 89 | @Override 90 | public String getAuthToken() throws AuthFailureError { 91 | AccountManagerFuture future = 92 | mAccountManager.getAuthToken( 93 | mAccount, 94 | mAuthTokenType, 95 | mNotifyAuthFailure, 96 | /* callback= */ null, 97 | /* handler= */ null); 98 | Bundle result; 99 | try { 100 | result = future.getResult(); 101 | } catch (Exception e) { 102 | throw new AuthFailureError("Error while retrieving auth token", e); 103 | } 104 | String authToken = null; 105 | if (future.isDone() && !future.isCancelled()) { 106 | if (result.containsKey(AccountManager.KEY_INTENT)) { 107 | Intent intent = result.getParcelable(AccountManager.KEY_INTENT); 108 | throw new AuthFailureError(intent); 109 | } 110 | authToken = result.getString(AccountManager.KEY_AUTHTOKEN); 111 | } 112 | if (authToken == null) { 113 | throw new AuthFailureError("Got null auth token for type: " + mAuthTokenType); 114 | } 115 | 116 | return authToken; 117 | } 118 | 119 | @Override 120 | public void invalidateAuthToken(String authToken) { 121 | mAccountManager.invalidateAuthToken(mAccount.type, authToken); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /library/src/main/java/com/remoteconfig/library/ExecutorDelivery.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.remoteconfig.library; 18 | 19 | import android.os.Handler; 20 | 21 | import java.util.concurrent.Executor; 22 | 23 | /** Delivers responses and errors. */ 24 | public class ExecutorDelivery implements ResponseDelivery { 25 | /** Used for posting responses, typically to the main thread. */ 26 | private final Executor mResponsePoster; 27 | 28 | /** 29 | * Creates a new response delivery interface. 30 | * 31 | * @param handler {@link Handler} to post responses on 32 | */ 33 | public ExecutorDelivery(final Handler handler) { 34 | // Make an Executor that just wraps the handler. 35 | mResponsePoster = 36 | new Executor() { 37 | @Override 38 | public void execute(Runnable command) { 39 | handler.post(command); 40 | } 41 | }; 42 | } 43 | 44 | /** 45 | * Creates a new response delivery interface, mockable version for testing. 46 | * 47 | * @param executor For running delivery tasks 48 | */ 49 | public ExecutorDelivery(Executor executor) { 50 | mResponsePoster = executor; 51 | } 52 | 53 | @Override 54 | public void postResponse(Request request, Response response) { 55 | postResponse(request, response, null); 56 | } 57 | 58 | @Override 59 | public void postResponse(Request request, Response response, Runnable runnable) { 60 | request.markDelivered(); 61 | request.addMarker("post-response"); 62 | mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable)); 63 | } 64 | 65 | @Override 66 | public void postError(Request request, RemoteError error) { 67 | request.addMarker("post-error"); 68 | Response response = Response.error(error); 69 | mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, null)); 70 | } 71 | 72 | /** A Runnable used for delivering network responses to a listener on the main thread. */ 73 | @SuppressWarnings("rawtypes") 74 | private static class ResponseDeliveryRunnable implements Runnable { 75 | private final Request mRequest; 76 | private final Response mResponse; 77 | private final Runnable mRunnable; 78 | 79 | public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) { 80 | mRequest = request; 81 | mResponse = response; 82 | mRunnable = runnable; 83 | } 84 | 85 | @SuppressWarnings("unchecked") 86 | @Override 87 | public void run() { 88 | // NOTE: If cancel() is called off the thread that we're currently running in (by 89 | // default, the main thread), we cannot guarantee that deliverResponse()/deliverError() 90 | // won't be called, since it may be canceled after we check isCanceled() but before we 91 | // deliver the response. Apps concerned about this guarantee must either call cancel() 92 | // from the same thread or implement their own guarantee about not invoking their 93 | // listener after cancel() has been called. 94 | 95 | // If this request has canceled, finish it and don't deliver. 96 | if (mRequest.isCanceled()) { 97 | mRequest.finish("canceled-at-delivery"); 98 | return; 99 | } 100 | 101 | // Deliver a normal response or error, depending. 102 | if (mResponse.isSuccess()) { 103 | mRequest.deliverResponse(mResponse.result); 104 | } else { 105 | mRequest.deliverError(mResponse.error); 106 | } 107 | 108 | // If this is an intermediate response, add a marker, otherwise we're done 109 | // and the request can be finished. 110 | if (mResponse.intermediate) { 111 | mRequest.addMarker("intermediate-response"); 112 | } else { 113 | mRequest.finish("done"); 114 | } 115 | 116 | // If we have been provided a post-delivery runnable, run it. 117 | if (mRunnable != null) { 118 | mRunnable.run(); 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 19 | 20 | 25 | 26 | 29 | 30 | 36 | 37 | 38 | 43 | 44 | 49 | 50 | 53 | 54 | 60 | 61 | 62 | 63 | 68 | 69 | 74 | 75 | 76 | 79 | 80 | 86 | 87 | 88 | 93 | 94 | 99 | 100 | 101 | 104 | 105 | 111 | 112 | 113 | 118 | 119 | 124 | 125 | 126 | 129 | 130 | 136 | 137 | 138 |