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 
17 package com.android.locationtracker;
18 
19 import com.android.locationtracker.data.TrackerDataHelper;
20 
21 import android.app.Service;
22 import android.content.BroadcastReceiver;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.content.SharedPreferences;
27 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
28 import android.location.Location;
29 import android.location.LocationListener;
30 import android.location.LocationManager;
31 import android.net.ConnectivityManager;
32 import android.net.wifi.ScanResult;
33 import android.net.wifi.WifiManager;
34 import android.os.Bundle;
35 import android.os.IBinder;
36 import android.preference.PreferenceManager;
37 import android.telephony.CellLocation;
38 import android.telephony.PhoneStateListener;
39 import android.telephony.SignalStrength;
40 import android.telephony.TelephonyManager;
41 import android.telephony.cdma.CdmaCellLocation;
42 import android.telephony.gsm.GsmCellLocation;
43 import android.util.Log;
44 import android.widget.Toast;
45 
46 import java.util.ArrayList;
47 import java.util.HashSet;
48 import java.util.List;
49 import 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  */
57 public 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 
TrackerService()90     public TrackerService() {
91     }
92 
93     @Override
onBind(Intent intent)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
onStart(Intent intent, int startId)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 
initLocationListeners()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 
getTrackedProviders()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 
getPreferences()183     private SharedPreferences getPreferences() {
184         return PreferenceManager.getDefaultSharedPreferences(this);
185     }
186 
trackNetwork()187     private boolean trackNetwork() {
188         return getPreferences().getBoolean(NETWORK_PREF, true);
189     }
190 
trackGPS()191     private boolean trackGPS() {
192         return getPreferences().getBoolean(GPS_PREF, true);
193     }
194 
doDebugLogging()195     private boolean doDebugLogging() {
196         return getPreferences().getBoolean(DEBUG_PREF, false);
197     }
198 
trackSignalStrength()199     private boolean trackSignalStrength() {
200         return getPreferences().getBoolean(SIGNAL_PREF, false);
201     }
202 
getLocationMinDistance()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 
getLocationUpdateTime()214     private long getLocationUpdateTime() {
215         try {
216             String timeString = getPreferences().getString(MIN_TIME_PREF, "0");
217             long secondsTime = Long.valueOf(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
onDestroy()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      */
stopListeners()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 
getLocationManager()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      */
getDistanceFromNetwork(Location location)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          */
onLocationChanged(Location location)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          */
onProviderDisabled(String provider)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          */
onProviderEnabled(String provider)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          */
onStatusChanged(String provider, int status, Bundle extras)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
onReceive(Context context, Intent intent)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 
onSharedPreferenceChanged( SharedPreferences sharedPreferences, String key)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