Java tutorial
/* Copyright (C) 2017 Daniele Gobbetti This file is part of Gadgetbridge. Gadgetbridge is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Gadgetbridge is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview; import android.annotation.TargetApi; import android.content.Intent; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Message; import android.os.RemoteException; import android.webkit.WebResourceRequest; import android.webkit.WebResourceResponse; import android.webkit.WebView; import android.webkit.WebViewClient; import net.e175.klaus.solarpositioning.DeltaT; import net.e175.klaus.solarpositioning.SPA; import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.ByteArrayInputStream; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CountDownLatch; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.model.Weather; import static nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton.internetHelper; import static nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton.internetHelperBound; import static nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton.internetHelperListener; import static nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton.internetResponse; import static nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton.latch; public class GBWebClient extends WebViewClient { private String[] AllowedDomains = new String[] { "openweathermap.org", //for weather :) "tagesschau.de" //for internal watchapp tests }; private static final Logger LOG = LoggerFactory.getLogger(GBWebClient.class); @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { Uri parsedUri = request.getUrl(); LOG.debug("WEBVIEW shouldInterceptRequest URL: " + parsedUri.toString()); WebResourceResponse mimickedReply = mimicReply(parsedUri); if (mimickedReply != null) return mimickedReply; return super.shouldInterceptRequest(view, request); } @Override public WebResourceResponse shouldInterceptRequest(WebView view, String url) { LOG.debug("WEBVIEW shouldInterceptRequest URL (legacy): " + url); Uri parsedUri = Uri.parse(url); WebResourceResponse mimickedReply = mimicReply(parsedUri); if (mimickedReply != null) return mimickedReply; return super.shouldInterceptRequest(view, url); } private WebResourceResponse mimicReply(Uri requestedUri) { if (requestedUri.getHost() != null && (org.apache.commons.lang3.StringUtils .indexOfAny(requestedUri.getHost(), AllowedDomains) != -1)) { if (internetHelperBound) { LOG.debug("WEBVIEW forwarding request to the internet helper"); Bundle bundle = new Bundle(); bundle.putString("URL", requestedUri.toString()); Message webRequest = Message.obtain(); webRequest.replyTo = internetHelperListener; webRequest.setData(bundle); try { latch = new CountDownLatch(1); //the messenger should run on a single thread, hence we don't need to be worried about concurrency. This approach however is certainly not ideal. internetHelper.send(webRequest); latch.await(); return internetResponse; } catch (RemoteException | InterruptedException e) { LOG.warn("Error downloading data from " + requestedUri, e); } } else { LOG.debug("WEBVIEW request to openweathermap.org detected of type: " + requestedUri.getPath() + " params: " + requestedUri.getQuery()); return mimicOpenWeatherMapResponse(requestedUri.getPath(), requestedUri.getQueryParameter("units")); } } else { LOG.debug("WEBVIEW request:" + requestedUri.toString() + " not intercepted"); } return null; } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { Uri parsedUri = Uri.parse(url); if (parsedUri.getScheme().startsWith("http")) { Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); GBApplication.getContext().startActivity(i); } else if (parsedUri.getScheme().startsWith("pebblejs")) { url = url.replaceFirst("^pebblejs://close#", "file:///android_asset/app_config/configure.html?config=true&json="); view.loadUrl(url); } else if (parsedUri.getScheme().equals("data")) { //clay view.loadUrl(url); } else { LOG.debug("WEBVIEW Ignoring unhandled scheme: " + parsedUri.getScheme()); } return true; } private static WebResourceResponse mimicOpenWeatherMapResponse(String type, String units) { if (Weather.getInstance() == null) { LOG.warn("WEBVIEW - Weather instance is null, cannot update weather"); return null; } CurrentPosition currentPosition = new CurrentPosition(); try { JSONObject resp = Weather.getInstance().createReconstructedOWMWeatherReply(); if ("/data/2.5/weather".equals(type) && resp != null) { JSONObject main = resp.getJSONObject("main"); convertTemps(main, units); //caller might want different units resp.put("cod", 200); resp.put("coord", coordObject(currentPosition)); resp.put("sys", sysObject(currentPosition)); // } else if ("/data/2.5/forecast".equals(type) && Weather.getInstance().getWeather2().reconstructedOWMForecast != null) { //this is wrong, as we only have daily data. Unfortunately it looks like daily forecasts cannot be reconstructed // resp = new JSONObject(Weather.getInstance().getWeather2().reconstructedOWMForecast.toString()); // // JSONObject city = resp.getJSONObject("city"); // city.put("coord", coordObject(currentPosition)); // // JSONArray list = resp.getJSONArray("list"); // for (int i = 0, size = list.length(); i < size; i++) { // JSONObject item = list.getJSONObject(i); // JSONObject main = item.getJSONObject("main"); // convertTemps(main, units); //caller might want different units // } // // resp.put("cod", 200); } else { LOG.warn("WEBVIEW - cannot mimick request of type " + type + " (unsupported or lack of data)"); return null; } LOG.info("WEBVIEW - mimic openweather response" + resp.toString()); Map<String, String> headers = new HashMap<>(); headers.put("Access-Control-Allow-Origin", "*"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return new WebResourceResponse("application/json", "utf-8", 200, "OK", headers, new ByteArrayInputStream(resp.toString().getBytes())); } else { return new WebResourceResponse("application/json", "utf-8", new ByteArrayInputStream(resp.toString().getBytes())); } } catch (JSONException e) { LOG.warn("Error building the JSON weather message.", e); } return null; } private static JSONObject sysObject(CurrentPosition currentPosition) throws JSONException { GregorianCalendar[] sunrise = SPA.calculateSunriseTransitSet(new GregorianCalendar(), currentPosition.getLatitude(), currentPosition.getLongitude(), DeltaT.estimate(new GregorianCalendar())); JSONObject sys = new JSONObject(); sys.put("country", "World"); sys.put("sunrise", (sunrise[0].getTimeInMillis() / 1000)); sys.put("sunset", (sunrise[2].getTimeInMillis() / 1000)); return sys; } private static void convertTemps(JSONObject main, String units) throws JSONException { if ("metric".equals(units)) { main.put("temp", (int) main.get("temp") - 273); main.put("temp_min", (int) main.get("temp_min") - 273); main.put("temp_max", (int) main.get("temp_max") - 273); } else if ("imperial".equals(units)) { //it's 2017... this is so sad main.put("temp", ((int) (main.get("temp")) - 273.15f) * 1.8f + 32); main.put("temp_min", ((int) (main.get("temp_min")) - 273.15f) * 1.8f + 32); main.put("temp_max", ((int) (main.get("temp_max")) - 273.15f) * 1.8f + 32); } } private static JSONObject coordObject(CurrentPosition currentPosition) throws JSONException { JSONObject coord = new JSONObject(); coord.put("lat", currentPosition.getLatitude()); coord.put("lon", currentPosition.getLongitude()); return coord; } }