/*
* Copyright (C) 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.apps.santatracker.service;
import android.support.annotation.NonNull;
import android.util.Log;
import com.google.android.apps.santatracker.BuildConfig;
import com.google.android.apps.santatracker.R;
import com.google.android.apps.santatracker.data.DestinationDbHelper;
import com.google.android.apps.santatracker.data.SantaPreferences;
import com.google.android.apps.santatracker.data.StreamDbHelper;
import com.google.android.apps.santatracker.data.Switches;
import com.google.android.apps.santatracker.util.SantaLog;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.remoteconfig.FirebaseRemoteConfig;
import com.google.firebase.remoteconfig.FirebaseRemoteConfigFetchThrottledException;
import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* ApiProcessor that loads data from the remote Santa API.
*/
public class RemoteApiProcessor extends APIProcessor {
private static final String TAG = "RemoteApiProcessor";
private static final Set<String> SUPPORTED_SANTA_HEADER_API = new HashSet<>(Arrays.asList("2", "2016"));
private static final String API_VERSION_FIELD = "X-Santa-Version";
private static final long DEFAULT_CACHE_EXPIRY_S = 60 * 12; // 5 requests / hr
private final FirebaseRemoteConfig mConfig;
private final FirebaseRemoteConfigSettings mConfigSettings;
private long mConfigCacheExpiry;
private long mThrottleEndTimeMillis = 0;
public RemoteApiProcessor(SantaPreferences mPreferences, DestinationDbHelper mDBHelper,
StreamDbHelper streamDbHelper, APICallback callback) {
super(mPreferences, mDBHelper, streamDbHelper, callback);
// Set Firebase remote config settings once
mConfig = FirebaseRemoteConfig.getInstance();
mConfigSettings = new FirebaseRemoteConfigSettings.Builder()
.setDeveloperModeEnabled(BuildConfig.DEBUG)
.build();
mConfig.setConfigSettings(mConfigSettings);
mConfig.setDefaults(R.xml.remote_config_defaults);
// Set cache expiration to 0s when debugging to allow easy testing, otherwise
// use the default value
mConfigCacheExpiry = mConfigSettings.isDeveloperModeEnabled() ? 0 : DEFAULT_CACHE_EXPIRY_S;
SantaLog.d(TAG, "Config Cache Expiry: " + mConfigCacheExpiry);
}
@Override
public JSONObject loadApi(String url) {
// Check Firebase Remote Config
long currentTime = System.currentTimeMillis();
if (currentTime > mThrottleEndTimeMillis) {
mConfig.fetch(mConfigCacheExpiry)
.addOnSuccessListener(new OnSuccessListener<Void>() {
@Override
public void onSuccess(Void aVoid) {
SantaLog.d(TAG, "fetchConfig:SUCCESS");
// Activate config and notify clients of any changes
mConfig.activateFetched();
checkSwitchesDiff(getSwitches());
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
if (e instanceof FirebaseRemoteConfigFetchThrottledException) {
// Store throttle end time
FirebaseRemoteConfigFetchThrottledException ex =
(FirebaseRemoteConfigFetchThrottledException) e;
mThrottleEndTimeMillis = ex.getThrottleEndTimeMillis();
SantaLog.w(TAG, "fetchConfig:THROTTLED until " + mThrottleEndTimeMillis);
} else {
SantaLog.w(TAG, "fetchConfig:UNEXPECTED_ERROR", e);
}
}
});
} else {
long msRemaining = mThrottleEndTimeMillis - currentTime;
Log.d(TAG, "Not trying config, throttled for " + msRemaining + "ms");
}
// Retrieve and parse the json data.
String data;
SantaLog.d(TAG, "Accessing API: "+url);
try {
data = downloadUrl(url);
} catch (IOException e1) {
Log.d(TAG, "Santa Communication Error 0");
return null;
}
// Check that data was retrieved
if (data == null) {
Log.d(TAG, "Santa Communication Error 1");
return null;
}
// parse data as json
try {
return new JSONObject(data);
} catch (JSONException e) {
Log.d(TAG, "Santa Communication Error 2");
}
return null;
}
@Override
protected Switches getSwitches() {
Switches switches = new Switches();
switches.disableCastButton = mConfig.getBoolean(FIELD_DISABLE_CASTBUTTON);
switches.disableDestinationPhoto = mConfig.getBoolean(FIELD_DISABLE_PHOTO);
// Old games
switches.gameState.disableGumballGame = mConfig.getBoolean(FIELD_DISABLE_GUMBALLGAME);
switches.gameState.disableJetpackGame = mConfig.getBoolean(FIELD_DISABLE_JETPACKGAME);
switches.gameState.disableMemoryGame = mConfig.getBoolean(FIELD_DISABLE_MEMORYGAME);
switches.gameState.disableRocketGame = mConfig.getBoolean(FIELD_DISABLE_ROCKETGAME);
switches.gameState.disableDancerGame = mConfig.getBoolean(FIELD_DISABLE_DANCERGAME);
// Snowdown
switches.gameState.disableSnowdownGame = mConfig.getBoolean(FIELD_DISABLE_SNOWDOWNGAME);
// Doodles
switches.gameState.disableSwimmingGame = mConfig.getBoolean(FIELD_DISABLE_SWIMMINGGAME);
switches.gameState.disableBmxGame = mConfig.getBoolean(FIELD_DISABLE_BMXGAME);
switches.gameState.disableRunningGame = mConfig.getBoolean(FIELD_DISABLE_RUNNINGGAME);
switches.gameState.disableTennisGame = mConfig.getBoolean(FIELD_DISABLE_TENNISGAME);
switches.gameState.disableWaterpoloGame = mConfig.getBoolean(FIELD_DISABLE_WATERPOLOGAME);
// City Quiz
switches.gameState.disableCityQuizGame = mConfig.getBoolean(FIELD_DISABLE_CITY_QUIZ);
// Present Quest
switches.gameState.disablePresentQuest = mConfig.getBoolean(FIELD_DISABLE_PRESENTQUEST);
// Videos
switches.video1 = mConfig.getString(FIELD_VIDEO_1);
switches.video15 = mConfig.getString(FIELD_VIDEO_15);
switches.video23 = mConfig.getString(FIELD_VIDEO_23);
return switches;
}
/**
* Downloads the given URL and return
*/
protected String downloadUrl(String myurl) throws IOException {
InputStream is = null;
// Only display the first 500 characters of the retrieved
// web page content.
// int len = 500;
try {
URL url = new URL(myurl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(10000);
conn.setConnectTimeout(15000);
conn.setRequestMethod("GET");
conn.setDoInput(true);
// Starts the query
conn.connect();
int response = conn.getResponseCode();
if (!isValidHeader(conn)) {
// not a valid header
Log.d(TAG, "Santa communication failure.");
return null;
} else if (response != 200) {
Log.d(TAG, "Santa communication failure " + response);
return null;
} else {
is = conn.getInputStream();
// Convert the InputStream into a string
return read(is).toString();
}
// Makes sure that the InputStream is closed
} finally {
if (is != null) {
is.close();
}
}
}
/**
* Returns true if this application can handle requests of this version,
* false otherwise. The current API version can be retrieved through:
* <code>curl -sI 'http://santa-api.appspot.com/info' | grep X-Santa</code>
*/
protected boolean isValidHeader(HttpURLConnection connection) {
String version = connection.getHeaderField(API_VERSION_FIELD);
// if the version matches supported version, returns true, false if no
// header is set or it is not recognised.
return version != null && SUPPORTED_SANTA_HEADER_API.contains(version);
}
}