1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.locationtracker;
18
19import com.android.locationtracker.data.TrackerDataHelper;
20
21import android.app.Service;
22import android.content.BroadcastReceiver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.content.SharedPreferences;
27import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
28import android.location.Location;
29import android.location.LocationListener;
30import android.location.LocationManager;
31import android.net.ConnectivityManager;
32import android.net.wifi.ScanResult;
33import android.net.wifi.WifiManager;
34import android.os.Bundle;
35import android.os.IBinder;
36import android.preference.PreferenceManager;
37import android.telephony.CellLocation;
38import android.telephony.PhoneStateListener;
39import android.telephony.SignalStrength;
40import android.telephony.TelephonyManager;
41import android.telephony.cdma.CdmaCellLocation;
42import android.telephony.gsm.GsmCellLocation;
43import android.util.Log;
44import android.widget.Toast;
45
46import java.util.ArrayList;
47import java.util.HashSet;
48import java.util.List;
49import java.util.Set;
50
51/**
52 * Location Tracking service
53 *
54 * Records location updates for all registered location providers, and cell
55 * location updates
56 */
57public class TrackerService extends Service {
58
59    private List<LocationTrackingListener> mListeners;
60
61    private static final String LOG_TAG = TrackerActivity.LOG_TAG;
62
63    // controls which location providers to track
64    private Set<String> mTrackedProviders;
65
66    private TrackerDataHelper mTrackerData;
67
68    private TelephonyManager mTelephonyManager;
69    private Location mNetworkLocation;
70
71    // Handlers and Receivers for phone and network state
72    private NetworkStateBroadcastReceiver mNetwork;
73    private static final String CELL_PROVIDER_TAG = "cell";
74    // signal strength updates
75    private static final String SIGNAL_PROVIDER_TAG = "signal";
76    private static final String WIFI_PROVIDER_TAG = "wifi";
77    // tracking tag for data connectivity issues
78    private static final String DATA_CONN_PROVIDER_TAG = "data";
79
80    // preference constants
81    private static final String MIN_TIME_PREF = "mintime_preference";
82    private static final String MIN_DIS_PREF = "mindistance_preference";
83    private static final String GPS_PREF = "gps_preference";
84    private static final String NETWORK_PREF = "network_preference";
85    private static final String SIGNAL_PREF = "signal_preference";
86    private static final String DEBUG_PREF = "advanced_log_preference";
87
88    private PreferenceListener mPrefListener;
89
90    public TrackerService() {
91    }
92
93    @Override
94    public IBinder onBind(Intent intent) {
95        // ignore - nothing to do
96        return null;
97    }
98
99    /**
100     * registers location listeners
101     *
102     * @param intent
103     * @param startId
104     */
105    @Override
106    public void onStart(Intent intent, int startId) {
107        super.onStart(intent, startId);
108        mNetworkLocation = null;
109
110        initLocationListeners();
111        Toast.makeText(this, "Tracking service started", Toast.LENGTH_SHORT);
112    }
113
114    private synchronized void initLocationListeners() {
115        mTrackerData = new TrackerDataHelper(this);
116        LocationManager lm = getLocationManager();
117
118        mTrackedProviders = getTrackedProviders();
119
120        List<String> locationProviders = lm.getAllProviders();
121        mListeners = new ArrayList<LocationTrackingListener>(
122                locationProviders.size());
123
124        long minUpdateTime = getLocationUpdateTime();
125        float minDistance = getLocationMinDistance();
126
127        for (String providerName : locationProviders) {
128            if (mTrackedProviders.contains(providerName)) {
129                Log.d(LOG_TAG, "Adding location listener for provider " +
130                        providerName);
131                if (doDebugLogging()) {
132                    mTrackerData.writeEntry("init", String.format(
133                            "start listening to %s : %d ms; %f meters",
134                            providerName, minUpdateTime, minDistance));
135                }
136                LocationTrackingListener listener =
137                    new LocationTrackingListener();
138                lm.requestLocationUpdates(providerName, minUpdateTime,
139                        minDistance, listener);
140                mListeners.add(listener);
141            }
142        }
143        mTelephonyManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
144
145        if (doDebugLogging()) {
146            // register for cell location updates
147            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CELL_LOCATION);
148
149            // Register for Network (Wifi or Mobile) updates
150            mNetwork = new NetworkStateBroadcastReceiver();
151            IntentFilter mIntentFilter;
152            mIntentFilter = new IntentFilter();
153            mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
154            mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
155            mIntentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
156            Log.d(LOG_TAG, "registering receiver");
157            registerReceiver(mNetwork, mIntentFilter);
158        }
159
160        if (trackSignalStrength()) {
161            mTelephonyManager.listen(mPhoneStateListener,
162                    PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
163        }
164
165        // register for preference changes, so we can restart listeners on
166        // pref changes
167        mPrefListener = new PreferenceListener();
168        getPreferences().registerOnSharedPreferenceChangeListener(mPrefListener);
169    }
170
171    private Set<String> getTrackedProviders() {
172        Set<String> providerSet = new HashSet<String>();
173
174        if (trackGPS()) {
175            providerSet.add(LocationManager.GPS_PROVIDER);
176        }
177        if (trackNetwork()) {
178            providerSet.add(LocationManager.NETWORK_PROVIDER);
179        }
180        return providerSet;
181    }
182
183    private SharedPreferences getPreferences() {
184        return PreferenceManager.getDefaultSharedPreferences(this);
185    }
186
187    private boolean trackNetwork() {
188        return getPreferences().getBoolean(NETWORK_PREF, true);
189    }
190
191    private boolean trackGPS() {
192        return getPreferences().getBoolean(GPS_PREF, true);
193    }
194
195    private boolean doDebugLogging() {
196        return getPreferences().getBoolean(DEBUG_PREF, false);
197    }
198
199    private boolean trackSignalStrength() {
200        return getPreferences().getBoolean(SIGNAL_PREF, false);
201    }
202
203    private float getLocationMinDistance() {
204        try {
205            String disString = getPreferences().getString(MIN_DIS_PREF, "0");
206            return Float.parseFloat(disString);
207        }
208        catch (NumberFormatException e) {
209            Log.e(LOG_TAG, "Invalid preference for location min distance", e);
210        }
211        return 0;
212    }
213
214    private long getLocationUpdateTime() {
215        try {
216            String timeString = getPreferences().getString(MIN_TIME_PREF, "0");
217            long secondsTime = Long.parseLong(timeString);
218            return secondsTime * 1000;
219        }
220        catch (NumberFormatException e) {
221            Log.e(LOG_TAG, "Invalid preference for location min time", e);
222        }
223        return 0;
224    }
225
226    /**
227     * Shuts down this service
228     */
229    @Override
230    public void onDestroy() {
231        super.onDestroy();
232        Log.d(LOG_TAG, "Removing location listeners");
233        stopListeners();
234        Toast.makeText(this, "Tracking service stopped", Toast.LENGTH_SHORT);
235    }
236
237    /**
238     * De-registers all location listeners, closes persistent storage
239     */
240    protected synchronized void stopListeners() {
241        LocationManager lm = getLocationManager();
242        if (mListeners != null) {
243            for (LocationTrackingListener listener : mListeners) {
244                lm.removeUpdates(listener);
245            }
246            mListeners.clear();
247        }
248        mListeners = null;
249
250        // stop cell state listener
251        if (mTelephonyManager != null) {
252            mTelephonyManager.listen(mPhoneStateListener, 0);
253        }
254
255        // stop network/wifi listener
256        if (mNetwork != null) {
257            unregisterReceiver(mNetwork);
258        }
259        mNetwork = null;
260
261        mTrackerData = null;
262        if (mPrefListener != null) {
263            getPreferences().unregisterOnSharedPreferenceChangeListener(mPrefListener);
264            mPrefListener = null;
265        }
266    }
267
268    private LocationManager getLocationManager() {
269        return (LocationManager) getSystemService(Context.LOCATION_SERVICE);
270    }
271
272    /**
273     * Determine the current distance from given location to the last
274     * approximated network location
275     *
276     * @param location - new location
277     *
278     * @return float distance in meters
279     */
280    private synchronized float getDistanceFromNetwork(Location location) {
281        float value = 0;
282        if (mNetworkLocation != null) {
283            value = location.distanceTo(mNetworkLocation);
284        }
285        if (LocationManager.NETWORK_PROVIDER.equals(location.getProvider())) {
286            mNetworkLocation = location;
287        }
288        return value;
289    }
290
291    private class LocationTrackingListener implements LocationListener {
292
293        /**
294         * Writes details of location update to tracking file, including
295         * recording the distance between this location update and the last
296         * network location update
297         *
298         * @param location - new location
299         */
300        public void onLocationChanged(Location location) {
301            if (location == null) {
302                return;
303            }
304            float distance = getDistanceFromNetwork(location);
305            mTrackerData.writeEntry(location, distance);
306        }
307
308        /**
309         * Writes update to tracking file
310         *
311         * @param provider - name of disabled provider
312         */
313        public void onProviderDisabled(String provider) {
314            if (doDebugLogging()) {
315                mTrackerData.writeEntry(provider, "provider disabled");
316            }
317        }
318
319        /**
320         * Writes update to tracking file
321         *
322         * @param provider - name of enabled provider
323         */
324        public void onProviderEnabled(String provider) {
325            if (doDebugLogging()) {
326                mTrackerData.writeEntry(provider,  "provider enabled");
327            }
328        }
329
330        /**
331         * Writes update to tracking file
332         *
333         * @param provider - name of provider whose status changed
334         * @param status - new status
335         * @param extras - optional set of extra status messages
336         */
337        public void onStatusChanged(String provider, int status, Bundle extras) {
338            if (doDebugLogging()) {
339                mTrackerData.writeEntry(provider,  "status change: " + status);
340            }
341        }
342    }
343
344    PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
345        @Override
346        public void onCellLocationChanged(CellLocation location) {
347            try {
348                if (location instanceof GsmCellLocation) {
349                    GsmCellLocation cellLocation = (GsmCellLocation)location;
350                    String updateMsg = "cid=" + cellLocation.getCid() +
351                            ", lac=" + cellLocation.getLac();
352                    mTrackerData.writeEntry(CELL_PROVIDER_TAG, updateMsg);
353                } else if (location instanceof CdmaCellLocation) {
354                    CdmaCellLocation cellLocation = (CdmaCellLocation)location;
355                    String updateMsg = "BID=" + cellLocation.getBaseStationId() +
356                            ", SID=" + cellLocation.getSystemId() +
357                            ", NID=" + cellLocation.getNetworkId() +
358                            ", lat=" + cellLocation.getBaseStationLatitude() +
359                            ", long=" + cellLocation.getBaseStationLongitude() +
360                            ", SID=" + cellLocation.getSystemId() +
361                            ", NID=" + cellLocation.getNetworkId();
362                    mTrackerData.writeEntry(CELL_PROVIDER_TAG, updateMsg);
363                }
364            } catch (Exception e) {
365                Log.e(LOG_TAG, "Exception in CellStateHandler.handleMessage:", e);
366            }
367        }
368
369        public void onSignalStrengthsChanged(SignalStrength signalStrength) {
370            if (mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
371                String updateMsg = "cdma dBM=" + signalStrength.getCdmaDbm();
372                mTrackerData.writeEntry(SIGNAL_PROVIDER_TAG, updateMsg);
373            } else if  (mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
374                String updateMsg = "gsm signal=" + signalStrength.getGsmSignalStrength();
375                mTrackerData.writeEntry(SIGNAL_PROVIDER_TAG, updateMsg);
376            }
377        }
378    };
379
380    /**
381     * Listener + recorder for mobile or wifi updates
382     */
383    private class NetworkStateBroadcastReceiver extends BroadcastReceiver {
384        @Override
385        public void onReceive(Context context, Intent intent) {
386            String action = intent.getAction();
387
388            if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
389                WifiManager wifiManager =
390                    (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
391                List<ScanResult> wifiScanResults = wifiManager.getScanResults();
392                String updateMsg = "num scan results=" +
393                    (wifiScanResults == null ? "0" : wifiScanResults.size());
394                mTrackerData.writeEntry(WIFI_PROVIDER_TAG, updateMsg);
395
396            } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
397                String updateMsg;
398                boolean noConnectivity =
399                    intent.getBooleanExtra(
400                            ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
401                if (noConnectivity) {
402                    updateMsg = "no connectivity";
403                }
404                else {
405                    updateMsg = "connection available";
406                }
407                mTrackerData.writeEntry(DATA_CONN_PROVIDER_TAG, updateMsg);
408
409            } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
410                int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
411                    WifiManager.WIFI_STATE_UNKNOWN);
412
413                String stateString = "unknown";
414                switch (state) {
415                    case WifiManager.WIFI_STATE_DISABLED:
416                        stateString = "disabled";
417                        break;
418                    case WifiManager.WIFI_STATE_DISABLING:
419                        stateString = "disabling";
420                        break;
421                    case WifiManager.WIFI_STATE_ENABLED:
422                        stateString = "enabled";
423                        break;
424                    case WifiManager.WIFI_STATE_ENABLING:
425                        stateString = "enabling";
426                        break;
427                }
428                mTrackerData.writeEntry(WIFI_PROVIDER_TAG,
429                        "state = " + stateString);
430            }
431        }
432    }
433
434    private class PreferenceListener implements OnSharedPreferenceChangeListener {
435
436        public void onSharedPreferenceChanged(
437                SharedPreferences sharedPreferences, String key) {
438            Log.d(LOG_TAG, "restarting listeners due to preference change");
439            synchronized (TrackerService.this) {
440                stopListeners();
441                initLocationListeners();
442            }
443        }
444    }
445}
446