/* 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.client; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.location.Location; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.support.v4.content.LocalBroadcastManager; import org.json.JSONException; import org.mozilla.mozstumbler.client.mapview.MapFragment; import org.mozilla.mozstumbler.client.mapview.ObservationPoint; import org.mozilla.mozstumbler.service.core.logging.ClientLog; import org.mozilla.mozstumbler.service.stumblerthread.Reporter; import org.mozilla.mozstumbler.service.stumblerthread.datahandling.StumblerBundle; import org.mozilla.mozstumbler.service.utils.NetworkInfo; import org.mozilla.mozstumbler.svclocator.ServiceLocator; import org.mozilla.mozstumbler.svclocator.services.log.ILogger; import org.mozilla.mozstumbler.svclocator.services.log.LoggerUtil; import java.lang.ref.WeakReference; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; public class ObservedLocationsReceiver extends BroadcastReceiver { private static final ILogger Log = (ILogger) ServiceLocator.getInstance().getService(ILogger.class); private static final String LOG_TAG = LoggerUtil.makeLogTag(ObservedLocationsReceiver.class); private static final int MAX_QUEUED_MLS_POINTS_TO_FETCH = 10; private static final long FREQ_FETCH_MLS_MS = 5 * 1000; private static ObservedLocationsReceiver sInstance; private final List<ObservationPoint> mCollectionPoints = Collections.synchronizedList(new LinkedList<ObservationPoint>()); private final List<ObservationPoint> mQueuedForMLS = Collections.synchronizedList(new LinkedList<ObservationPoint>()); private final Handler mHandler = new Handler(Looper.getMainLooper()); // Upper bound on the size of the linked lists of points, for memory and performance safety. // On older devices, store fewer observations private final int MAX_SIZE_OF_POINT_LISTS = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) ? 5000 : 2500; private WeakReference<MapFragment> mMapActivity = new WeakReference<MapFragment>(null); private Context mContext; private ObservedLocationsReceiver() { } public static void createGlobalInstance(Context context) { sInstance = new ObservedLocationsReceiver(); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Reporter.ACTION_NEW_BUNDLE); LocalBroadcastManager.getInstance(context).registerReceiver(sInstance, intentFilter); } public static ObservedLocationsReceiver getInstance() { return sInstance; } public synchronized List<ObservationPoint> getObservationPoints_callerMustLock() { return mCollectionPoints; } // Must call when map activity stopped, to clear the reference to the map activity object public void removeMapActivity() { setMapActivity(null); } private synchronized MapFragment getMapActivity() { return mMapActivity.get(); } private final Runnable mFetchMLSRunnable = new Runnable() { @Override public void run() { synchronized (ObservedLocationsReceiver.this) { if (mContext == null) { return; } ClientPrefs prefs = ClientPrefs.getInstance(mContext); NetworkInfo networkInfo = new NetworkInfo(mContext); mHandler.postDelayed(mFetchMLSRunnable, FREQ_FETCH_MLS_MS); if (mQueuedForMLS.size() < 1 || !prefs.showMLSQueryResults()) { return; } int count = 0; List<ObservationPoint> queued; synchronized (mQueuedForMLS) { queued = new LinkedList<ObservationPoint>(mQueuedForMLS); } Iterator<ObservationPoint> li = queued.iterator(); while (li.hasNext() && count < MAX_QUEUED_MLS_POINTS_TO_FETCH) { ObservationPoint obs = li.next(); if (obs.needsToFetchMLS() && mContext != null) { obs.fetchMLS(networkInfo.isConnected(), networkInfo.isWifiAvailable()); count++; } else { if (getMapActivity() != null && obs.pointMLS != null) { getMapActivity().newMLSPoint(obs); } li.remove(); } } } } }; // Must be called by map activity when it is showing to get points displayed public synchronized void setMapActivity(MapFragment m) { mMapActivity = new WeakReference<MapFragment>(m); } @Override public void onReceive(Context context, Intent intent) { if (mContext == null) { // the first time here, start this queue handling runnable mHandler.postDelayed(mFetchMLSRunnable, FREQ_FETCH_MLS_MS); } mContext = context; ClientPrefs prefs = ClientPrefs.getInstance(context); if (prefs == null) { return; } final String action = intent.getAction(); if (!action.equals(Reporter.ACTION_NEW_BUNDLE)) { return; } final StumblerBundle bundle = intent.getParcelableExtra(Reporter.NEW_BUNDLE_ARG_BUNDLE); if (bundle == null) { return; } Location position = bundle.getGpsPosition(); if (position == null) { return; } ObservationPoint observation = new ObservationPoint(position); observation.mTrackSegment = bundle.getTrackSegment(); try { observation.setCounts(bundle.toMLSGeosubmit()); boolean getInfoForMLS = prefs.showMLSQueryResults() && bundle.hasRadioData(); if (getInfoForMLS) { observation.setMLSQuery(bundle); if (mQueuedForMLS.size() < MAX_SIZE_OF_POINT_LISTS) { mQueuedForMLS.add(0, observation); } } } catch (JSONException e) { ClientLog.w(LOG_TAG, "Failed to convert bundle to JSON: " + e); } synchronized(mCollectionPoints) { while (mCollectionPoints.size() > MAX_SIZE_OF_POINT_LISTS) { mCollectionPoints.remove(0); } if (mCollectionPoints.size() > 0 && !observation.pointGPS.hasBearing()) { final Location previous = mCollectionPoints.get(mCollectionPoints.size() - 1).pointGPS; observation.pointGPS.setBearing(previous.bearingTo(observation.pointGPS)); } mCollectionPoints.add(observation); } if (getMapActivity() == null) { return; } if (bundle.hasRadioData()) { getMapActivity().getActivity().runOnUiThread(new Runnable() { @Override public void run() { addObservationPointToMap(); } }); } } private void addObservationPointToMap() { if (getMapActivity() == null) { return; } synchronized (mCollectionPoints) { getMapActivity().newObservationPoint(mCollectionPoints.get(mCollectionPoints.size() - 1)); } } }