com.tonikorin.cordova.plugin.LocationProvider.LocationService.java Source code

Java tutorial

Introduction

Here is the source code for com.tonikorin.cordova.plugin.LocationProvider.LocationService.java

Source

/*
 Author: Toni Korin
    
 Licensed to the Apache Software Foundation (ASF) under one
 or more contributor license agreements.  See the NOTICE file
 distributed with this work for additional information
 regarding copyright ownership.  The ASF licenses this file
 to you 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.tonikorin.cordova.plugin.LocationProvider;

import android.app.IntentService;
import android.content.Intent;
import android.os.Bundle;
import android.content.Context;
import android.content.SharedPreferences;
import android.location.Location;
import android.os.SystemClock;
import android.util.Log;
import org.json.JSONObject;
import org.json.JSONArray;
import org.json.JSONException;

import java.net.HttpURLConnection;
import java.util.Calendar;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
import java.io.OutputStream;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.Date;
import java.text.SimpleDateFormat;

import com.tonikorin.cordova.plugin.LocationProvider.MyLocation;
import com.tonikorin.cordova.plugin.LocationProvider.MyLocation.LocationResult;

import static java.lang.Math.*;

public class LocationService extends IntentService {
    private static final String TAG = "LocationService";
    private final MyLocationResult myLocationResult = new MyLocationResult();
    private JSONObject config = null;
    private Context myContext;
    public static final String PREFS_NAME = "LocationService";
    public static final String CONFIG_NAME = "config";
    public static final String HISTORY_NAME = "history";
    private static final String ALIVE = "ALIVE";
    private static final String POSITION = "POSITION";
    private static final String FAILURE = "FAILURE";
    private static final String RESERVED = "RESERVED";
    private static final String CHAT = "CHAT";
    private static final String LOCATE = "LOCATE";

    public LocationService() {
        super("LocationService");
    }

    // The IntentService calls this method from the default worker thread with the intent that
    // started the service. When this method returns, IntentService stops the service.
    @Override
    protected void onHandleIntent(Intent intent) {
        try {
            myContext = LocationService.this;
            // read the configuration
            if (this.config == null) {
                String jsonString = myContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
                        .getString(CONFIG_NAME, "{}");
                config = new JSONObject(jsonString);
            }
            // get the input message from intent
            Bundle extras = intent.getExtras();
            String msgJsonStr = extras.getString("data"); // own "data" property inside "data" property
            JSONObject messageIn = new JSONObject(msgJsonStr);
            String time = extras.getString("time", "");
            handleLocationQuery(messageIn, time);
        } catch (Exception e) {
            Log.e(TAG, "onHandleIntent exception ", e);
        }
    }

    private void handleLocationQuery(JSONObject messageIn, String time) throws JSONException, IOException {
        Log.d(TAG, "Handle location query...");
        String ownName = config.optString("member", "");
        JSONObject teams = config.optJSONObject("teams");
        String teamName;
        String teamPassword;
        String teamHost;
        JSONObject team = null;
        if (teams != null)
            team = teams.optJSONObject(messageIn.optString("teamId"));
        if (team != null) {
            teamName = team.optString("name", "");
            teamPassword = team.optString("password", "");
            ownName = team.optString("member", ownName);
            teamHost = team.optString("host", "");
        } else
            return; // => URI hanging in PostServer
        String msgType = messageIn.optString("messageType", LOCATE);
        // Create Messaging Server interface
        String messageUrl = config.optString("messageUrl", "").replace("{host}", teamHost);
        MessageServer msgServer = new MessageServer(ownName, teamName, teamPassword, messageUrl);
        if (messageIn.optString("memberName").equals(ownName)) {
            updateLocateHistory(messageIn, true, msgType, time);
            msgServer.post(RESERVED);
            SystemClock.sleep(5000);// 5 sec delay
            String pushUrl = config.optString("pushUrl", "").replace("{host}", teamHost);
            msgServer.updatePushToken(ownName, teamName, config.optString("token", ""), pushUrl);
            return;
        }
        // Read extra team configuration (e.g. icon and schedule)
        TeamConfig cTeam = new TeamConfig(config, teamName);
        // Store details about location query
        updateLocateHistory(messageIn, cTeam.isBlocked(), msgType, time);
        if (cTeam.isBlocked())
            msgServer.addBlockedField();
        msgServer.post(ALIVE);
        if (cTeam.isBlocked() || CHAT.equals(msgType))
            return; // skip giving your location
        try {
            //Log.d(TAG, "myContext: " + myContext.getPackageName());
            new MyLocation(myContext, myLocationResult, messageIn.optInt("accuracy", 50),
                    config.optInt("timeout", 60)).start();
            JSONObject location = myLocationResult.getJsonLocation();
            //Log.d(TAG, "Background position accuracy: " + location.optInt("accuracy"));
            if (cTeam.getIcon() != null)
                msgServer.addIconField(cTeam.getIcon());
            msgServer.post(POSITION, location.toString());
        } catch (Exception e) {
            Log.e(TAG, "LocationProvider exception ", e);
            msgServer.post(FAILURE, e.getMessage());
        }
        Log.d(TAG, "Handle location query...completed!");
    }

    private String getDateAndTimeString(long utcTime) {
        Date date = new Date(utcTime);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(date);
    }

    private void updateLocateHistory(JSONObject messageIn, boolean blocked, String msgType, String time)
            throws JSONException { // Read current history
        SharedPreferences sp = myContext.getSharedPreferences(LocationService.PREFS_NAME, Context.MODE_PRIVATE);
        String historyJsonStr = sp.getString(LocationService.HISTORY_NAME, "{}");
        JSONObject history = new JSONObject(historyJsonStr);
        if (CHAT.equals(msgType)) { // CHAT history, save hole message
            JSONArray chatMessages = history.optJSONArray("chatMessages");
            if (chatMessages == null)
                chatMessages = new JSONArray();
            //Log.d(TAG, "CHAT history TIME: " + time);
            messageIn.put("time", Long.parseLong(time));
            chatMessages.put(messageIn.toString());
            if (chatMessages.length() > 100)
                chatMessages.remove(0); // store only last 100 chat messages
            history.put("chatMessages", chatMessages);
        } else {// Current LOCATE
            String member = messageIn.optString("memberName", "");
            if (blocked)
                member = "\u2717 " + member;
            JSONObject updateStatus = new JSONObject();
            updateStatus.put("member", member);
            updateStatus.put("team", messageIn.optString("teamId", ""));
            updateStatus.put("date", getDateAndTimeString(System.currentTimeMillis()));
            updateStatus.put("target", messageIn.optString("target", ""));
            history.put("updateStatus", updateStatus);
            // History LOCATE lines...
            JSONArray historyLines = history.optJSONArray("lines");
            if (historyLines == null)
                historyLines = new JSONArray();
            String target;
            if (updateStatus.getString("target").equals(""))
                target = updateStatus.optString("team");
            else
                target = updateStatus.optString("target");
            String historyLine = updateStatus.getString("member") + " (" + target + ") "
                    + updateStatus.getString("date") + "\n";
            historyLines.put(historyLine);
            if (historyLines.length() > 200)
                historyLines.remove(0); // store only last 200 locate queries
            history.put("lines", historyLines);
        }
        // Save new history
        SharedPreferences.Editor editor = sp.edit();
        editor.putString(LocationService.HISTORY_NAME, history.toString());
        editor.commit();
        //Log.d(TAG, "history:" + history.toString());
    }

    private class MessageServer {
        private HttpURLConnection con;
        private JSONObject messageOut;
        private OutputStream os;
        private String xChannel;
        private String urlString;
        private static final String TOKEN = "TOKEN";
        //private InputStream is;

        public MessageServer(String ownName, String teamName, String teamPassword, String urlMessageServer)
                throws JSONException, IOException { // Create message template for location data
            this.messageOut = new JSONObject();
            this.messageOut.put("memberName", ownName);
            this.messageOut.put("teamId", teamName);
            this.xChannel = "/channel/" + teamName + ":" + teamPassword;
            this.urlString = urlMessageServer;
        }

        public void updatePushToken(String ownName, String teamName, String pushToken, String urlPushServer)
                throws JSONException, IOException { // Create message template for push server and post it
            this.messageOut = new JSONObject();
            this.messageOut.put("name", ownName);
            this.messageOut.put("team", teamName);
            this.messageOut.put("token", pushToken);
            this.messageOut.put("tokenType", 3); // GCM
            this.urlString = urlPushServer;
            this.post(TOKEN);
        }

        public void post(String messageType) throws JSONException, IOException {
            Log.d(TAG, "POST: " + messageType);
            if (!messageType.equals(TOKEN))// messageType property not needed in PushServer
                messageOut.put("messageType", messageType);
            try { // Create connection and request
                URL url = new URL(urlString);
                if (url.getProtocol().equals("https"))
                    con = (HttpsURLConnection) url.openConnection();
                else
                    con = (HttpURLConnection) url.openConnection();
                con.setRequestMethod("POST");
                con.setDoOutput(true); // = POST method
                //con.setDoInput(true);
                con.setRequestProperty("Content-Type", "application/json; charset=utf-8");
                con.setRequestProperty("Accept", "application/json");
                con.setRequestProperty("X-channel", xChannel);
                con.setChunkedStreamingMode(0);
                // Send POST with JSON message as string
                os = con.getOutputStream();
                os.write(messageOut.toString().getBytes("UTF-8"));
                os.flush();
                Log.d(TAG, "POST response code:" + con.getResponseCode());
            } finally {
                os.close();
                con.disconnect(); // connection just moved to pool
            }
        }

        public void post(String messageType, String content) throws JSONException, IOException {
            messageOut.put("content", content);
            post(messageType);
        }

        public void addIconField(String icon) throws JSONException {
            messageOut.put("icon", icon);
        }

        public void addBlockedField() throws JSONException {
            messageOut.put("blocked", true);
        }
    }

    private class MyLocationResult extends LocationResult {
        private final CountDownLatch locationLatch = new CountDownLatch(1);
        private Location location = null;
        private static final int ROUND6 = 1000000;

        @Override
        public void gotLocation(Location location) {
            this.location = location;
            locationLatch.countDown(); // release await in getJsonLocation
        }

        public JSONObject getJsonLocation() throws JSONException, InterruptedException {
            locationLatch.await(2, TimeUnit.MINUTES); // wait max 2 minutes
            JSONObject loc = new JSONObject();
            if (location == null)
                throw new InterruptedException();
            loc.put("latitude", (double) round(location.getLatitude() * ROUND6) / ROUND6); // 6 decimals = 1m accuracy
            loc.put("longitude", (double) round(location.getLongitude() * ROUND6) / ROUND6);
            loc.put("accuracy", round(location.getAccuracy()));
            loc.put("altitude", round(location.getAltitude()));
            loc.put("altitudeAccuracy", "-"); // not supported in Android
            loc.put("heading", round(location.getBearing()));
            loc.put("speed", round(location.getSpeed()));
            loc.put("timestamp", getDateAndTimeString(location.getTime()));
            loc.put("mTime", location.getTime());
            return loc;
        }
    }

    private class TeamConfig {
        private String icon = null;
        private boolean blocked = false;

        public TeamConfig(JSONObject conf, String teamName) {
            JSONObject cTeams = conf.optJSONObject("cTeams");
            JSONObject cTeam = null;
            if (cTeams == null)
                return;
            else
                cTeam = cTeams.optJSONObject(teamName);
            if (cTeam != null) {
                icon = cTeam.optString("icon", null);
                long startDate = cTeam.optLong("startDate", 0); // epoch
                long endDate = cTeam.optLong("endDate", 0);
                int startTime = cTeam.optInt("startTime", 0); // total minutes from day start
                int endTime = cTeam.optInt("endTime", 0);
                String repeat = cTeam.optString("repeat", ""); // weekdays 0-6
                blocked = isLocationBlocked(startDate, endDate, startTime, endTime, repeat);
            }
        }

        public String getIcon() {
            return icon;
        }

        public boolean isBlocked() {
            return blocked;
        }

        private boolean isLocationBlocked(long startDate, long endDate, int startTime, int endTime, String repeat) {
            if (startDate > 0 || endDate > 0 || !repeat.isEmpty()) {
                Calendar c = Calendar.getInstance();
                long time = c.getTimeInMillis();
                if (startDate > 0) {
                    long start = startDate + (startTime * 60 * 1000);
                    if (time < start)
                        return true;
                }
                if (endDate > 0) {
                    long end = endDate + (endTime * 60 * 1000);
                    if (time > end)
                        return true;
                }
                if (!repeat.isEmpty()) {
                    int nowInMinutes = c.get(Calendar.HOUR_OF_DAY) * 60 + c.get(Calendar.MINUTE);
                    if (startTime == 0 && endTime == 0)
                        endTime = 1440; // hole day if not set
                    int day = c.get(Calendar.DAY_OF_WEEK) - 1;
                    int index = repeat.indexOf(Integer.toString(day));
                    if (startTime > endTime && index == -1) { // check also previous day
                        int prevDay = day - 1;
                        if (prevDay == -1)
                            prevDay = 6;
                        index = repeat.indexOf(Integer.toString(prevDay));
                    }
                    if (index == -1)
                        return true;
                    if (startTime < endTime) {
                        if (nowInMinutes < startTime || nowInMinutes > endTime)
                            return true;
                    } else {
                        if (nowInMinutes < startTime && nowInMinutes > endTime)
                            return true;
                    }
                }
            }
            return false;
        }
    }

}