<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.mozstumbler.service.stumblerthread.datahandling;

import android.annotation.TargetApi;
import android.location.Location;
import android.net.wifi.ScanResult;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.mozstumbler.service.AppGlobals;
import org.mozilla.mozstumbler.service.stumblerthread.scanners.cellscanner.CellInfo;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;

/**
 * A StumblerBundle contains stumbling data related to a single GPS lat/long fix.
 */
public final class StumblerBundle implements Parcelable {
    /* The maximum number of Wi-Fi access points in a single observation. */
    public static final int MAX_WIFIS_PER_LOCATION = 200;
    /* The maximum number of cells in a single observation */
    public static final int MAX_CELLS_PER_LOCATION = 50;
    private final Location mGpsPosition;
    private int mTrackSegment = -1;
    private final TreeMap<String, ScanResult> mWifiData;
    private final TreeMap<String, CellInfo> mCellData;
    private float mPressureHPA;

    public StumblerBundle(Location position) {
        mGpsPosition = position;
        mWifiData = new TreeMap<String, ScanResult>();
        mCellData = new TreeMap<String, CellInfo>();
    }

    public StumblerBundle(Location position, int trackSegment) {
        this(position);
        mTrackSegment = trackSegment;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel out, int flags) {
        Bundle wifiBundle = new Bundle(ScanResult.class.getClassLoader());
        Collection<String> scans = mWifiData.keySet();
        for (String s : scans) {
            wifiBundle.putParcelable(s, mWifiData.get(s));
        }

        Bundle cellBundle = new Bundle(CellInfo.class.getClassLoader());
        Collection<String> cells = mCellData.keySet();
        for (String c : cells) {
            cellBundle.putParcelable(c, mCellData.get(c));
        }

        out.writeBundle(wifiBundle);
        out.writeBundle(cellBundle);
        out.writeParcelable(mGpsPosition, 0);
        out.writeInt(mTrackSegment);
    }

    public Location getGpsPosition() {
        return mGpsPosition;
    }

    public int getTrackSegment() {
        return mTrackSegment;
    }

    public boolean hasRadioData() {
        boolean hasCellData = mCellData != null && !mCellData.isEmpty();
        boolean hasWifiData = mWifiData != null && !mWifiData.isEmpty();
        return hasCellData || hasWifiData;
    }

    public Map<String, ScanResult> getUnmodifiableWifiData() {
        if (mWifiData == null) {
            return null;
        }
        return Collections.unmodifiableMap(mWifiData);
    }

    public Map<String, CellInfo> getUnmodifiableCellData() {
        if (mCellData == null) {
            return null;
        }
        return Collections.unmodifiableMap(mCellData);
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    public MLSJSONObject toMLSGeosubmit() throws JSONException {
        MLSJSONObject headerFields = new MLSJSONObject();
        headerFields.put(DataStorageConstants.ReportsColumns.LAT, Math.floor(mGpsPosition.getLatitude() * 1.0E6) / 1.0E6);
        headerFields.put(DataStorageConstants.ReportsColumns.LON, Math.floor(mGpsPosition.getLongitude() * 1.0E6) / 1.0E6);
        headerFields.put(DataStorageConstants.ReportsColumns.TIME, mGpsPosition.getTime());
        headerFields.put(DataStorageConstants.ReportsColumns.HEADING,mGpsPosition.getBearing());
        headerFields.put(DataStorageConstants.ReportsColumns.SPEED,  mGpsPosition.getSpeed());
        if (mPressureHPA != 0.0) {
            headerFields.put(DataStorageConstants.ReportsColumns.PRESSURE,  mPressureHPA);
        }

        if (mCellData.size() > 0) {
            JSONArray cellJSON = new JSONArray();

            for (CellInfo c : mCellData.values()) {
                JSONObject obj = c.toJSONObject();
                cellJSON.put(obj);
            }
            headerFields.put(DataStorageConstants.ReportsColumns.CELL, cellJSON);
        }

        if (mWifiData.size() > 0) {
            JSONArray wifis = new JSONArray();

            long gpsTimeSinceBootInMS = (mGpsPosition.getElapsedRealtimeNanos()/1000000);
            for (ScanResult scanResult : mWifiData.values()) {
                JSONObject wifiEntry = new JSONObject();
                wifiEntry.put("macAddress", scanResult.BSSID);
                if (scanResult.frequency != 0) {
                    wifiEntry.put("frequency", scanResult.frequency);
                }
                if (scanResult.level != 0) {
                    wifiEntry.put("signalStrength", scanResult.level);
                }

                long wifiTimeSinceBootInMS = (scanResult.timestamp / 1000);
                long ageMS =  wifiTimeSinceBootInMS - gpsTimeSinceBootInMS;
                wifiEntry.put("age", ageMS);
                wifis.put(wifiEntry);
            }
            headerFields.put(DataStorageConstants.ReportsColumns.WIFI, wifis);
        }

        if (mGpsPosition.hasAltitude()) {
            headerFields.put(DataStorageConstants.ReportsColumns.ALTITUDE, (float) mGpsPosition.getAltitude());
        }
        if (mGpsPosition.hasAccuracy()) {
            // Note that Android does not support an accuracy measurement specific to altitude
            headerFields.put(DataStorageConstants.ReportsColumns.ACCURACY, mGpsPosition.getAccuracy());
        }

        return headerFields;
    }

    public JSONObject toMLSGeolocate() throws JSONException {
        JSONObject headerFields = new JSONObject();

        JSONObject fallbackPayload = new JSONObject();
        fallbackPayload.put("lacf", false);
        fallbackPayload.put("ipf", false);
        headerFields.put("fallbacks", fallbackPayload);

        headerFields.put(DataStorageConstants.ReportsColumns.LAT, Math.floor(mGpsPosition.getLatitude() * 1.0E6) / 1.0E6);
        headerFields.put(DataStorageConstants.ReportsColumns.LON, Math.floor(mGpsPosition.getLongitude() * 1.0E6) / 1.0E6);
        headerFields.put(DataStorageConstants.ReportsColumns.TIME, mGpsPosition.getTime());<commit_msg>put an API 17 guard around grabbing the age for wifi signals<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.mozstumbler.service.stumblerthread.datahandling;

import android.annotation.TargetApi;
import android.location.Location;
import android.net.wifi.ScanResult;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.mozstumbler.service.AppGlobals;
import org.mozilla.mozstumbler.service.stumblerthread.scanners.cellscanner.CellInfo;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;

/**
 * A StumblerBundle contains stumbling data related to a single GPS lat/long fix.
 */
public final class StumblerBundle implements Parcelable {
    /* The maximum number of Wi-Fi access points in a single observation. */
    public static final int MAX_WIFIS_PER_LOCATION = 200;
    /* The maximum number of cells in a single observation */
    public static final int MAX_CELLS_PER_LOCATION = 50;
    private final Location mGpsPosition;
    private int mTrackSegment = -1;
    private final TreeMap<String, ScanResult> mWifiData;
    private final TreeMap<String, CellInfo> mCellData;
    private float mPressureHPA;

    public StumblerBundle(Location position) {
        mGpsPosition = position;
        mWifiData = new TreeMap<String, ScanResult>();
        mCellData = new TreeMap<String, CellInfo>();
    }

    public StumblerBundle(Location position, int trackSegment) {
        this(position);
        mTrackSegment = trackSegment;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel out, int flags) {
        Bundle wifiBundle = new Bundle(ScanResult.class.getClassLoader());
        Collection<String> scans = mWifiData.keySet();
        for (String s : scans) {
            wifiBundle.putParcelable(s, mWifiData.get(s));
        }

        Bundle cellBundle = new Bundle(CellInfo.class.getClassLoader());
        Collection<String> cells = mCellData.keySet();
        for (String c : cells) {
            cellBundle.putParcelable(c, mCellData.get(c));
        }

        out.writeBundle(wifiBundle);
        out.writeBundle(cellBundle);
        out.writeParcelable(mGpsPosition, 0);
        out.writeInt(mTrackSegment);
    }

    public Location getGpsPosition() {
        return mGpsPosition;
    }

    public int getTrackSegment() {
        return mTrackSegment;
    }

    public boolean hasRadioData() {
        boolean hasCellData = mCellData != null && !mCellData.isEmpty();
        boolean hasWifiData = mWifiData != null && !mWifiData.isEmpty();
        return hasCellData || hasWifiData;
    }

    public Map<String, ScanResult> getUnmodifiableWifiData() {
        if (mWifiData == null) {
            return null;
        }
        return Collections.unmodifiableMap(mWifiData);
    }

    public Map<String, CellInfo> getUnmodifiableCellData() {
        if (mCellData == null) {
            return null;
        }
        return Collections.unmodifiableMap(mCellData);
    }

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    public MLSJSONObject toMLSGeosubmit() throws JSONException {
        MLSJSONObject headerFields = new MLSJSONObject();
        headerFields.put(DataStorageConstants.ReportsColumns.LAT, Math.floor(mGpsPosition.getLatitude() * 1.0E6) / 1.0E6);
        headerFields.put(DataStorageConstants.ReportsColumns.LON, Math.floor(mGpsPosition.getLongitude() * 1.0E6) / 1.0E6);
        headerFields.put(DataStorageConstants.ReportsColumns.TIME, mGpsPosition.getTime());
        headerFields.put(DataStorageConstants.ReportsColumns.HEADING,mGpsPosition.getBearing());
        headerFields.put(DataStorageConstants.ReportsColumns.SPEED,  mGpsPosition.getSpeed());
        if (mPressureHPA != 0.0) {
            headerFields.put(DataStorageConstants.ReportsColumns.PRESSURE,  mPressureHPA);
        }

        if (mCellData.size() > 0) {
            JSONArray cellJSON = new JSONArray();

            for (CellInfo c : mCellData.values()) {
                JSONObject obj = c.toJSONObject();
                cellJSON.put(obj);
            }
            headerFields.put(DataStorageConstants.ReportsColumns.CELL, cellJSON);
        }

        if (mWifiData.size() > 0) {
            JSONArray wifis = new JSONArray();
            long gpsTimeSinceBootInMS = 0;

            if (Build.VERSION.SDK_INT >= 17) {
                gpsTimeSinceBootInMS = (mGpsPosition.getElapsedRealtimeNanos() / 1000000);
            }

            for (ScanResult scanResult : mWifiData.values()) {
                JSONObject wifiEntry = new JSONObject();
                wifiEntry.put("macAddress", scanResult.BSSID);
                if (scanResult.frequency != 0) {
                    wifiEntry.put("frequency", scanResult.frequency);
                }
                if (scanResult.level != 0) {
                    wifiEntry.put("signalStrength", scanResult.level);
                }

                if (Build.VERSION.SDK_INT >= 17) {
                    long wifiTimeSinceBootInMS = (scanResult.timestamp / 1000);
                    long ageMS =  wifiTimeSinceBootInMS - gpsTimeSinceBootInMS;
                    wifiEntry.put("age", ageMS);
                }

                wifis.put(wifiEntry);
            }
            headerFields.put(DataStorageConstants.ReportsColumns.WIFI, wifis);
        }

        if (mGpsPosition.hasAltitude()) {
            headerFields.put(DataStorageConstants.ReportsColumns.ALTITUDE, (float) mGpsPosition.getAltitude());
        }
        if (mGpsPosition.hasAccuracy()) {
            // Note that Android does not support an accuracy measurement specific to altitude
            headerFields.put(DataStorageConstants.ReportsColumns.ACCURACY, mGpsPosition.getAccuracy());
        }

        return headerFields;
    }

    public JSONObject toMLSGeolocate() throws JSONException {
        JSONObject headerFields = new JSONObject();

        JSONObject fallbackPayload = new JSONObject();
        fallbackPayload.put("lacf", false);
        fallbackPayload.put("ipf", false);
        headerFields.put("fallbacks", fallbackPayload);

        headerFields.put(DataStorageConstants.ReportsColumns.LAT, Math.floor(mGpsPosition.getLatitude() * 1.0E6) / 1.0E6);
        headerFields.put(DataStorageConstants.ReportsColumns.LON, Math.floor(mGpsPosition.getLongitude() * 1.0E6) / 1.0E6);
        headerFields.put(DataStorageConstants.ReportsColumns.TIME, mGpsPosition.getTime());
{'fraction_non_alphanumeric': 0.09805238415043653, 'fraction_numerical': 0.00858144914558615, 'mean_word_length': 2.772023641992682, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4363269', 'n_tokens_mistral': 4045, 'n_tokens_neox': 3846, 'n_words': 805}