org.microg.nlp.backend.ichnaea.BackendService.java Source code

Java tutorial

Introduction

Here is the source code for org.microg.nlp.backend.ichnaea.BackendService.java

Source

/*
 * Copyright 2013-2016 microG Project Team
 *
 * 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 org.microg.nlp.backend.ichnaea;

import android.content.SharedPreferences;
import android.location.Location;
import android.os.Build;
import android.os.Process;
import android.preference.PreferenceManager;
import android.util.Log;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.microg.nlp.api.CellBackendHelper;
import org.microg.nlp.api.HelperLocationBackendService;
import org.microg.nlp.api.LocationHelper;
import org.microg.nlp.api.WiFiBackendHelper;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Set;

import static org.microg.nlp.api.CellBackendHelper.Cell;
import static org.microg.nlp.api.WiFiBackendHelper.WiFi;

public class BackendService extends HelperLocationBackendService
        implements WiFiBackendHelper.Listener, CellBackendHelper.Listener {

    private static final String TAG = "IchnaeaBackendService";
    private static final String SERVICE_URL = "https://location.services.mozilla.com/v1/geolocate?key=%s";
    private static final String API_KEY = "068ab754-c06b-473d-a1e5-60e7b1a2eb77";
    private static final String PROVIDER = "ichnaea";
    private static final int RATE_LIMIT_MS = 5000;

    private static BackendService instance;

    private boolean running = false;
    private Set<WiFi> wiFis;
    private Set<Cell> cells;
    private Thread thread;
    private long lastRequest = 0;

    private boolean useWiFis = true;
    private boolean useCells = true;

    @Override
    public synchronized void onCreate() {
        super.onCreate();
        reloadSettings();
        reloadInstanceSettings();
    }

    @Override
    protected synchronized void onOpen() {
        super.onOpen();
        reloadSettings();
        instance = this;
        running = true;
        Log.d(TAG, "Activating instance at process " + Process.myPid());
    }

    public static void reloadInstanceSettings() {
        if (instance != null) {
            instance.reloadSettings();
        } else {
            Log.d(TAG, "No instance found active.");
        }
    }

    private void reloadSettings() {
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
        removeHelpers();
        if (preferences.getBoolean("use_cells", true)
                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            addHelper(new CellBackendHelper(this, this));
        } else {
            cells = null;
        }
        if (preferences.getBoolean("use_wifis", true)) {
            addHelper(new WiFiBackendHelper(this, this));
        } else {
            wiFis = null;
        }
    }

    @Override
    protected synchronized void onClose() {
        super.onClose();
        running = false;
        if (instance == this) {
            instance = null;
            Log.d(TAG, "Deactivating instance at process " + Process.myPid());
        }
    }

    @Override
    public void onWiFisChanged(Set<WiFi> wiFis) {
        this.wiFis = wiFis;
        if (running)
            startCalculate();
    }

    @Override
    public void onCellsChanged(Set<Cell> cells) {
        this.cells = cells;
        Log.d(TAG, "Cells: " + cells.size());
        if (running)
            startCalculate();
    }

    private synchronized void startCalculate() {
        if (thread != null)
            return;
        if (lastRequest + RATE_LIMIT_MS > System.currentTimeMillis())
            return;
        final Set<WiFi> wiFis = this.wiFis;
        final Set<Cell> cells = this.cells;
        if ((cells == null || cells.isEmpty()) && (wiFis == null || wiFis.size() < 2))
            return;
        thread = new Thread(new Runnable() {
            @Override
            public void run() {
                HttpURLConnection conn = null;
                try {
                    conn = (HttpURLConnection) new URL(String.format(SERVICE_URL, API_KEY)).openConnection();
                    conn.setDoOutput(true);
                    conn.setDoInput(true);
                    String request = createRequest(cells, wiFis);
                    Log.d(TAG, "request: " + request);
                    conn.getOutputStream().write(request.getBytes());
                    String r = new String(readStreamToEnd(conn.getInputStream()));
                    Log.d(TAG, "response: " + r);
                    JSONObject response = new JSONObject(r);
                    double lat = response.getJSONObject("location").getDouble("lat");
                    double lon = response.getJSONObject("location").getDouble("lng");
                    double acc = response.getDouble("accuracy");
                    report(LocationHelper.create(PROVIDER, lat, lon, (float) acc));
                } catch (IOException | JSONException e) {
                    if (conn != null) {
                        InputStream is = conn.getErrorStream();
                        if (is != null) {
                            try {
                                String error = new String(readStreamToEnd(is));
                                Log.w(TAG, "Error: " + error);
                            } catch (Exception ignored) {
                            }
                        }
                    }
                    Log.w(TAG, e);
                }

                lastRequest = System.currentTimeMillis();
                thread = null;
            }
        });
        thread.start();
    }

    @Override
    public void report(Location location) {
        Log.d(TAG, "reporting: " + location);
        super.report(location);
    }

    private static byte[] readStreamToEnd(InputStream is) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        if (is != null) {
            byte[] buff = new byte[1024];
            while (true) {
                int nb = is.read(buff);
                if (nb < 0) {
                    break;
                }
                bos.write(buff, 0, nb);
            }
            is.close();
        }
        return bos.toByteArray();
    }

    /**
     * see https://mozilla-ichnaea.readthedocs.org/en/latest/cell.html
     */
    @SuppressWarnings("MagicNumber")
    private static int calculateAsu(Cell cell) {
        switch (cell.getType()) {
        case GSM:
            return Math.max(0, Math.min(31, (cell.getSignal() + 113) / 2));
        case UMTS:
            return Math.max(-5, Math.max(91, cell.getSignal() + 116));
        case LTE:
            return Math.max(0, Math.min(95, cell.getSignal() + 140));
        case CDMA:
            int signal = cell.getSignal();
            if (signal >= -75) {
                return 16;
            }
            if (signal >= -82) {
                return 8;
            }
            if (signal >= -90) {
                return 4;
            }
            if (signal >= -95) {
                return 2;
            }
            if (signal >= -100) {
                return 1;
            }
            return 0;
        }
        return 0;
    }

    private static String getRadioType(Cell cell) {
        switch (cell.getType()) {
        case CDMA:
            return "cdma";
        case LTE:
            return "lte";
        case UMTS:
            return "wcdma";
        case GSM:
        default:
            return "gsm";
        }
    }

    private static String createRequest(Set<Cell> cells, Set<WiFi> wiFis) throws JSONException {
        JSONObject jsonObject = new JSONObject();
        JSONArray cellTowers = new JSONArray();

        if (cells != null) {
            Cell.CellType lastType = null;
            for (Cell cell : cells) {
                if (cell.getType() == Cell.CellType.CDMA) {
                    jsonObject.put("radioType", "cdma");
                } else if (lastType != null && lastType != cell.getType()) {
                    // We can't contribute if different cell types are mixed.
                    jsonObject.put("radioType", null);
                } else {
                    jsonObject.put("radioType", getRadioType(cell));
                }
                lastType = cell.getType();
                JSONObject cellTower = new JSONObject();
                cellTower.put("radioType", getRadioType(cell));
                cellTower.put("mobileCountryCode", cell.getMcc());
                cellTower.put("mobileNetworkCode", cell.getMnc());
                cellTower.put("locationAreaCode", cell.getLac());
                cellTower.put("cellId", cell.getCid());
                cellTower.put("signalStrength", cell.getSignal());
                if (cell.getPsc() != -1)
                    cellTower.put("psc", cell.getPsc());
                cellTower.put("asu", calculateAsu(cell));
                cellTowers.put(cellTower);
            }
        }
        JSONArray wifiAccessPoints = new JSONArray();
        if (wiFis != null) {
            for (WiFi wiFi : wiFis) {
                JSONObject wifiAccessPoint = new JSONObject();
                wifiAccessPoint.put("macAddress", wiFi.getBssid());
                //wifiAccessPoint.put("age", age);
                if (wiFi.getChannel() != -1)
                    wifiAccessPoint.put("channel", wiFi.getChannel());
                if (wiFi.getFrequency() != -1)
                    wifiAccessPoint.put("frequency", wiFi.getFrequency());
                wifiAccessPoint.put("signalStrength", wiFi.getRssi());
                //wifiAccessPoint.put("signalToNoiseRatio", signalToNoiseRatio);
                wifiAccessPoints.put(wifiAccessPoint);
            }
        }
        jsonObject.put("cellTowers", cellTowers);
        jsonObject.put("wifiAccessPoints", wifiAccessPoints);
        jsonObject.put("fallbacks", new JSONObject().put("lacf", true).put("ipf", false));
        return jsonObject.toString();
    }
}