package net.maxbraun.mirror; import android.content.Context; import android.util.Log; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Locale; import java.util.concurrent.TimeUnit; import net.maxbraun.mirror.Body.BodyMeasure; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; /** * A helper class to regularly retrieve body measurements. */ public class Body extends DataUpdater<BodyMeasure[]> { private static final String TAG = Body.class.getSimpleName(); /** * The time in milliseconds between API calls to update the body measures. */ private static final long UPDATE_INTERVAL_MILLIS = TimeUnit.MINUTES.toMillis(5); /** * The context used to load string resources. */ private final Context context; /** * A timestamped body measure data point. */ public static class BodyMeasure { /** * The unix timestamp in seconds when the measure was taken. */ public final long timestamp; /** * The body weight in kilograms. */ public final double weight; public BodyMeasure(long timestamp, double weight) { this.timestamp = timestamp; this.weight = weight; } } public Body(Context context, UpdateListener<BodyMeasure[]> updateListener) { super(updateListener, UPDATE_INTERVAL_MILLIS); this.context = context; } @Override protected BodyMeasure[] getData() { // Get the latest data from the Withings API. String requestUrl = getRequestUrl(); // Parse the data we are interested in from the response JSON. try { JSONObject response = Network.getJson(requestUrl); if (response != null) { return parseBodyMeasures(response); } else { return null; } } catch (JSONException e) { Log.e(TAG, "Failed to parse weather JSON.", e); return null; } } /** * Reads the body measure data points from the API response. API documentation: * http://oauth.withings.com/api/doc */ private static BodyMeasure[] parseBodyMeasures(JSONObject response) throws JSONException { int status = response.getInt("status"); if (status != 0) { Log.e(TAG, "Error status in response: " + status); return null; } JSONObject body = response.getJSONObject("body"); JSONArray measureGroups = body.getJSONArray("measuregrps"); // Iterate over all measures in the response. List<BodyMeasure> bodyMeasures = new ArrayList<>(); for (int i = 0; i < measureGroups.length(); i++) { JSONObject measureGroup = measureGroups.getJSONObject(i); long date = measureGroup.getLong("date"); JSONArray measures = measureGroup.getJSONArray("measures"); for (int j = 0; j < measures.length(); j++) { JSONObject measure = measures.getJSONObject(j); // We only care about the weight. int type = measure.getInt("type"); if (type != 1) { continue; } // Decode the weight. int value = measure.getInt("value"); int unit = measure.getInt("unit"); double weight = value * Math.pow(10, unit); // Add this measure to the list. BodyMeasure bodyMeasure = new BodyMeasure(date, weight); bodyMeasures.add(bodyMeasure); } } // Make sure the measures are sorted by ascending timestamp. Collections.sort(bodyMeasures, new Comparator<BodyMeasure>() { @Override public int compare(BodyMeasure lhs, BodyMeasure rhs) { return Long.compare(lhs.timestamp, rhs.timestamp); } }); return bodyMeasures.toArray(new BodyMeasure[bodyMeasures.size()]); } /** * Creates the URL for a Withings API request based on the current time. */ private String getRequestUrl() { long requestTimestamp = System.currentTimeMillis() / 1000; long startTimestamp = getStartTimestamp(); return String.format(Locale.US, "http://wbsapi.withings.net/measure" + "?action=getmeas" + "&oauth_version=1.0" + "&oauth_signature_method=HMAC-SHA1" + "&oauth_consumer_key=%s" + "&oauth_nonce=%s" + "&oauth_signature=%s" + "&oauth_token=%s" + "&oauth_timestamp=%d" + "&userid=%s" + "&startdate=%d" + "&meastype=1", context.getString(R.string.withings_consumer_key), context.getString(R.string.withings_nonce), context.getString(R.string.withings_signature), context.getString(R.string.withings_token), requestTimestamp, context.getString(R.string.withings_userid), startTimestamp); } /** * Calculates the start timestamp (six months before today) in Unix epoch seconds. */ private static long getStartTimestamp() { Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.add(Calendar.MONTH, -6); return calendar.getTime().getTime() / 1000; } @Override protected String getTag() { return TAG; } }