Java tutorial
/* * Copyright 2014 Randy McEoin * * 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 net.mceoin.cominghome; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.preference.PreferenceManager; import android.support.v4.app.NotificationCompat; import android.support.v4.app.TaskStackBuilder; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.VolleyError; import com.android.volley.VolleyLog; import com.android.volley.toolbox.JsonObjectRequest; import net.mceoin.cominghome.history.HistoryUpdate; import net.mceoin.cominghome.oauth.OAuthFlowApp; import net.mceoin.cominghome.structures.StructuresUpdate; import org.apache.http.HttpStatus; import org.apache.http.client.ClientProtocolException; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.HttpURLConnection; import java.net.URL; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.TimeZone; /** * Nest utility functions * <p/> * Some handy info is at: * <p/> * http://stackoverflow.com/questions/24601798/acquiring-and-changing-basic-data-on-the-nest-thermostat */ public class NestUtils { public static final String TAG = NestUtils.class.getSimpleName(); public static final boolean debug = false; public static final String MSG_ETA = "eta"; public static final String MSG_AWAY = "away"; public static final String GOT_INFO = "net.mceoin.cominghome.NetUtils.GotInfo"; public static final String LOST_AUTH = "net.mceoin.cominghome.NetUtils.LostAuth"; public static void getInfo(final Context context, String access_token) { if (debug) Log.d(TAG, "getInfo()"); if (context == null) { Log.e(TAG, "missing context"); return; } // Tag used to cancel the request String tag_update_status = "nest_info_req"; String url = "https://developer-api.nest.com/structures?auth=" + access_token; JsonObjectRequest updateStatusReq = new JsonObjectRequest(Request.Method.GET, url, null, new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { if (debug) Log.d(TAG, "response=" + response.toString()); // { // "structure_id":"njBTS-gAhF1mJ8_oF23ne7JNDyx1m1hULWixOD6IQWEe-SFA", // "thermostats":["n232323jy8Xr1HVc2OGqICVP45i-Mc"], // "smoke_co_alarms":["pt00ag34grggZchI7ICVPddi-Mc"], // "country_code":"US", // "away":"home" // "name":"Home" // } String structure_id = ""; String structure_name = ""; String away_status = ""; SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(AppController.getInstance().getApplicationContext()); boolean structure_id_selected = prefs.contains(MainActivity.PREFS_STRUCTURE_ID); String structure_id_current = prefs.getString(MainActivity.PREFS_STRUCTURE_ID, ""); boolean current_in_structures = false; String last_structure_name = ""; String last_away_status = ""; HashSet<String> structure_ids = new HashSet<>(); // use this for faking additional structures if (debug) { StructuresUpdate.update(context, "demo_id", "Demo Structure", "home"); structure_ids.add("demo_id"); } JSONObject structures; try { structures = new JSONObject(response.toString()); Iterator<String> keys = structures.keys(); while (keys.hasNext()) { String structure = keys.next(); JSONObject value = structures.getJSONObject(structure); if (debug) Log.d(TAG, "value=" + value); structure_id = value.getString("structure_id"); structure_name = value.getString("name"); away_status = value.getString("away"); StructuresUpdate.update(context, structure_id, structure_name, away_status); structure_ids.add(structure_id); if (structure_id_selected) { if (structure_id.equals(structure_id_current)) { current_in_structures = true; // // found the structure_id that we're associated with // go ahead and update the name and away status // SharedPreferences.Editor pref = prefs.edit(); pref.putString(MainActivity.PREFS_STRUCTURE_NAME, structure_name); pref.putString(MainActivity.PREFS_LAST_AWAY_STATUS, away_status); pref.apply(); last_structure_name = structure_name; last_away_status = away_status; } } } } catch (JSONException e) { Log.e(TAG, "error parsing JSON"); return; } if (structure_id_selected) { if (!current_in_structures) { // there is a structure_id selected, but it is no longer in the structures // listed from Nest, so clear it out SharedPreferences.Editor pref = prefs.edit(); pref.remove(MainActivity.PREFS_STRUCTURE_ID); pref.remove(MainActivity.PREFS_STRUCTURE_NAME); pref.remove(MainActivity.PREFS_LAST_AWAY_STATUS); pref.apply(); } } else { // // No structure_id currently selected, so go ahead and pick the // last structure_id. This should work for most homes that will // only have one structure_id anyway. // SharedPreferences.Editor pref = prefs.edit(); pref.putString(MainActivity.PREFS_STRUCTURE_ID, structure_id); pref.putString(MainActivity.PREFS_STRUCTURE_NAME, structure_name); pref.putString(MainActivity.PREFS_LAST_AWAY_STATUS, away_status); pref.apply(); last_structure_name = structure_name; last_away_status = away_status; } HashSet<String> stored_structure_ids = StructuresUpdate.getStructureIds(context); for (String id : stored_structure_ids) { if (!structure_ids.contains(id)) { // // if we have a stored id that wasn't in the array Nest just sent us, // then delete it from our database // StructuresUpdate.deleteStructureId(context, id); } } Intent intent = new Intent(GOT_INFO); intent.putExtra("structure_name", last_structure_name); intent.putExtra("away_status", last_away_status); LocalBroadcastManager.getInstance(AppController.getInstance().getApplicationContext()) .sendBroadcast(intent); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { if (error.networkResponse != null) { if (debug) Log.d(TAG, "getInfo volley statusCode=" + error.networkResponse.statusCode); Context context = AppController.getInstance().getApplicationContext(); if (error.networkResponse.statusCode == HttpStatus.SC_UNAUTHORIZED) { // We must have been de-authorized at the Nest web site SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences.Editor pref = prefs.edit(); pref.putString(OAuthFlowApp.PREF_ACCESS_TOKEN, ""); pref.putString(MainActivity.PREFS_STRUCTURE_ID, ""); pref.putString(MainActivity.PREFS_STRUCTURE_NAME, ""); pref.putString(MainActivity.PREFS_LAST_AWAY_STATUS, ""); pref.apply(); HistoryUpdate.add(context, "Lost our Nest authorization"); Intent intent = new Intent(LOST_AUTH); LocalBroadcastManager .getInstance(AppController.getInstance().getApplicationContext()) .sendBroadcast(intent); } else { HistoryUpdate.add(context, error.getLocalizedMessage()); VolleyLog.d(TAG, "getInfo Error: " + error.getMessage()); } } } }); AppController.getInstance().addToRequestQueue(updateStatusReq, tag_update_status); } public static void sendETA(String access_token, Handler handler, String structure_id, String trip_id, int etaMinutes) { if (debug) Log.d(TAG, "sendETA(,,,," + etaMinutes + ")"); SendETA sendEta = new SendETA(); sendEta.setHandler(handler); sendEta.setStructureId(structure_id); sendEta.setTripId(trip_id); sendEta.setEtaMinutes(etaMinutes); sendEta.execute(access_token); } public static void sendAwayStatus(Context context, String access_token, Handler handler, String structure_id, String away_status) { if (debug) Log.d(TAG, "sendAwayStatus(,,," + away_status + ")"); if ((access_token == null) || (access_token.isEmpty())) { Log.e(TAG, "missing access_token"); return; } if ((structure_id == null) || (structure_id.isEmpty())) { Log.e(TAG, "missing structure_id"); return; } SendAwayStatus sendAwayStatus = new SendAwayStatus(); sendAwayStatus.setHandler(handler); sendAwayStatus.setStructureId(structure_id); sendAwayStatus.setAwayStatus(away_status); sendAwayStatus.setContext(context); sendAwayStatus.execute(access_token); } private static void logException(Exception e, String extra) { String msg = "Exception"; if ((e != null) && (e.getLocalizedMessage() != null)) { msg = "Exception: " + e.getLocalizedMessage(); } msg += " " + extra; Log.e(TAG, msg); } private static class SendETA extends AsyncTask<String, Integer, Double> { private String trip_id; private String structure_id; private int etaMinutes; private Handler handler; private boolean error = false; private String errorResult = ""; public void setHandler(Handler handler) { this.handler = handler; } public void setTripId(String trip_id) { this.trip_id = trip_id; } public void setStructureId(String structure_id) { this.structure_id = structure_id; } public void setEtaMinutes(int etaMinutes) { this.etaMinutes = etaMinutes; } @Override protected Double doInBackground(String... params) { postData(params[0]); return null; } protected void onPostExecute(Double result) { if (debug) { Log.d(TAG, "onPostExecute"); } Message msg = Message.obtain(); Bundle b = new Bundle(); b.putString("type", MSG_ETA); String status; if (error) { status = "error"; } else { status = "ok"; } b.putString("status", status); msg.setData(b); handler.sendMessage(msg); } protected void onProgressUpdate(Integer... progress) { // pb.setProgress(progress[0]); } public void postData(String access_token) { StringBuilder builder = new StringBuilder(); // curl -X PUT -d ' {"trip_id":"sample-trip-id","estimated_arrival_window_begin":"2014-07-04T10:48:11+00:00","estimated_arrival_window_end":"2014-07-04T18:48:11+00:00"}' //"http://developer-api.nest.com/structures/5af48890-b516-11e3-9eff-123139166438/eta.json?auth=c.VG6bfzyOxAltaih6P4v..." String urlString = "https://developer-api.nest.com/structures/" + structure_id + "/eta.json?auth=" + access_token; if (debug) { Log.d(TAG, "url=" + urlString); } HttpURLConnection urlConnection = null; try { URL url = new URL(urlString); urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.setRequestMethod("PUT"); urlConnection.setDoOutput(true); urlConnection.setDoInput(true); urlConnection.setChunkedStreamingMode(0); urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf8"); urlConnection.setRequestProperty("Accept", "application/json"); JSONObject keyArg = new JSONObject(); final long ONE_MINUTE_IN_MILLIS = 60000;//millisecs Date now = new Date(); long t = now.getTime(); Date begin = new Date(t + (etaMinutes * ONE_MINUTE_IN_MILLIS)); Date end = new Date(t + ((etaMinutes + 5) * ONE_MINUTE_IN_MILLIS)); // date must be in ISO 8601 format SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss+00:00"); TimeZone tz = TimeZone.getTimeZone("UTC"); formatter.setTimeZone(tz); String beginString = formatter.format(begin); String endString = formatter.format(end); keyArg.put("estimated_arrival_window_begin", beginString); keyArg.put("estimated_arrival_window_end", endString); keyArg.put("trip_id", trip_id); if (debug) { Log.d(TAG, "estimated_arrival_window_begin=" + beginString); Log.d(TAG, "estimated_arrival_window_end=" + endString); } OutputStreamWriter wr = new OutputStreamWriter(urlConnection.getOutputStream()); wr.write(keyArg.toString()); wr.flush(); if (debug) { Log.d(TAG, keyArg.toString()); } boolean redirect = false; // normally, 3xx is redirect int status = urlConnection.getResponseCode(); if (status != HttpURLConnection.HTTP_OK) { if (status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_MOVED_PERM || status == 307 // Temporary redirect || status == HttpURLConnection.HTTP_SEE_OTHER) redirect = true; } System.out.println("Response Code ... " + status); if (redirect) { // get redirect url from "location" header field String newUrl = urlConnection.getHeaderField("Location"); // open the new connnection again urlConnection = (HttpURLConnection) new URL(newUrl).openConnection(); urlConnection.setRequestMethod("PUT"); urlConnection.setDoOutput(true); urlConnection.setDoInput(true); urlConnection.setChunkedStreamingMode(0); urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf8"); urlConnection.setRequestProperty("Accept", "application/json"); System.out.println("Redirect to URL : " + newUrl); wr = new OutputStreamWriter(urlConnection.getOutputStream()); wr.write(keyArg.toString()); wr.flush(); } int statusCode = urlConnection.getResponseCode(); if (debug) { Log.d(TAG, "statusCode=" + statusCode); } if ((statusCode == 200) || (statusCode == 400)) { InputStream response; if (statusCode == 200) { response = urlConnection.getInputStream(); } else { response = urlConnection.getErrorStream(); error = true; } BufferedReader reader = new BufferedReader(new InputStreamReader(response)); String line; while ((line = reader.readLine()) != null) { builder.append(line); } if (debug) Log.d(TAG, "response=" + builder.toString()); if (statusCode == 400) { JSONObject object = new JSONObject(builder.toString()); Iterator<String> keys = object.keys(); while (keys.hasNext()) { String key = keys.next(); if ((key.equals("error"))) { errorResult = object.getString("error"); if (debug) { Log.d(TAG, "errorResult=" + errorResult); } } } } } else { error = true; } } catch (ClientProtocolException e) { logException(e, "SendETA: ClientProtocol"); } catch (IOException e) { logException(e, "SendETA: IO"); } catch (Exception e) { logException(e, "SendETA"); } finally { if (urlConnection != null) { urlConnection.disconnect(); } } } } private static class SendAwayStatus extends AsyncTask<String, Integer, Double> { private String structure_id; private String away_status; private Handler handler; private boolean error = false; private String errorResult = ""; private Context context; public void setContext(Context context) { this.context = context; } public void setHandler(Handler handler) { this.handler = handler; } public void setStructureId(String structure_id) { this.structure_id = structure_id; } public void setAwayStatus(String away_status) { this.away_status = away_status; } @Override protected Double doInBackground(String... params) { postData(params[0]); return null; } protected void onPostExecute(Double result) { if (debug) { Log.d(TAG, "onPostExecute"); } if (context != null) { String status; if (error) { status = away_status + " errored"; } else { status = away_status; } sendNotification(context, status); } if (handler != null) { Message msg = Message.obtain(); Bundle b = new Bundle(); b.putString("type", MSG_AWAY); String status; if (error) { status = "error"; } else { status = "ok"; } b.putString("status", status); msg.setData(b); handler.sendMessage(msg); } } protected void onProgressUpdate(Integer... progress) { // pb.setProgress(progress[0]); } public void postData(String access_token) { StringBuilder builder = new StringBuilder(); // curl -v -L -X PUT "https://developer-api.nest.com/structures/uD9iIYonC2ygFs54nyP468bBDsGdF8rRIG0AHPcn4dhimK7g/away?auth=c.bCYw5l7mvN1ogNPVe9ecomAlrht34NTnP7Rb10b01tBnlFUVfiVjF9x4z3CIpPXKaU7nO2owrAIhwb05HZTheRTRMPhV32GshhkWD9HrteWhC1XcfzP02xBL06wyqnSKl2PiKEHAG2ZQmair" // -H "Content-Type: application/json" -d '"away"' String urlString = "https://developer-api.nest.com/structures/" + structure_id + "/away?auth=" + access_token; if (debug) { Log.d(TAG, "url=" + urlString); } HttpURLConnection urlConnection = null; try { URL url = new URL(urlString); urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.setRequestProperty("User-Agent", "ComingHome/1.0"); urlConnection.setRequestMethod("PUT"); urlConnection.setDoOutput(true); urlConnection.setDoInput(true); urlConnection.setChunkedStreamingMode(0); urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf8"); // urlConnection.setRequestProperty("Accept", "application/json"); String payload = "\"" + away_status + "\""; JSONObject keyArg = new JSONObject(); keyArg.put("away", away_status); OutputStreamWriter wr = new OutputStreamWriter(urlConnection.getOutputStream()); wr.write(payload); wr.flush(); if (debug) { Log.d(TAG, keyArg.toString()); } boolean redirect = false; // normally, 3xx is redirect int status = urlConnection.getResponseCode(); if (status != HttpURLConnection.HTTP_OK) { if (status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_MOVED_PERM || status == 307 // Temporary redirect || status == HttpURLConnection.HTTP_SEE_OTHER) redirect = true; } System.out.println("Response Code ... " + status); if (redirect) { // get redirect url from "location" header field String newUrl = urlConnection.getHeaderField("Location"); // open the new connnection again urlConnection = (HttpURLConnection) new URL(newUrl).openConnection(); urlConnection.setRequestMethod("PUT"); urlConnection.setDoOutput(true); urlConnection.setDoInput(true); urlConnection.setChunkedStreamingMode(0); urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf8"); urlConnection.setRequestProperty("Accept", "application/json"); System.out.println("Redirect to URL : " + newUrl); wr = new OutputStreamWriter(urlConnection.getOutputStream()); wr.write(payload); wr.flush(); } int statusCode = urlConnection.getResponseCode(); if (debug) Log.d(TAG, "statusCode=" + statusCode); if ((statusCode == 200) || (statusCode == 400)) { InputStream response; if (statusCode == 200) { response = urlConnection.getInputStream(); if (debug) Log.d(TAG, "response=" + response); } else { response = urlConnection.getErrorStream(); error = true; BufferedReader reader = new BufferedReader(new InputStreamReader(response)); String line; while ((line = reader.readLine()) != null) { builder.append(line); } if (debug) Log.d(TAG, "response=" + builder.toString()); JSONObject object = new JSONObject(builder.toString()); Iterator<String> keys = object.keys(); while (keys.hasNext()) { String key = keys.next(); if (key.equals("error")) { errorResult = object.getString("error"); if (debug) { Log.d(TAG, "errorResult=" + errorResult); } } } } } else { error = true; } } catch (ClientProtocolException e) { logException(e, "SendAwayStatus: ClientProtocol"); } catch (IOException e) { logException(e, "SendAwayStatus: IO"); } catch (Exception e) { logException(e, "SendAwayStatus: other"); } finally { if (urlConnection != null) { urlConnection.disconnect(); } } } } /** * Posts a notification in the notification bar when a transition is detected. * If the user clicks the notification, control goes to the main Activity. * * @param transitionType The type of transition that occurred. */ public static void sendNotification(Context context, String transitionType) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); boolean notifications = prefs.getBoolean(MainActivity.PREFS_NOTIFICATIONS, true); if (!notifications) { if (debug) Log.d(TAG, "notifications are turned off"); return; } // Create an explicit content Intent that starts the main Activity Intent notificationIntent = new Intent(context, MainActivity.class); // Construct a task stack TaskStackBuilder stackBuilder = TaskStackBuilder.create(context); // Adds the main Activity to the task stack as the parent stackBuilder.addParentStack(MainActivity.class); // Push the content Intent onto the stack stackBuilder.addNextIntent(notificationIntent); // Get a PendingIntent containing the entire back stack PendingIntent notificationPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); // Get a notification builder that's compatible with platform versions >= 4 NotificationCompat.Builder builder = new NotificationCompat.Builder(context); // Set the notification contents builder.setSmallIcon(R.drawable.home) .setContentTitle(context.getString(R.string.nest_transition_notification_title, transitionType)) .setContentText(context.getString(R.string.nest_transition_notification_text)) .setContentIntent(notificationPendingIntent); // Get an instance of the Notification manager NotificationManager mNotificationManager = (NotificationManager) context .getSystemService(Context.NOTIFICATION_SERVICE); // Issue the notification mNotificationManager.notify(0, builder.build()); } }