| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * 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 com.android.locationtracker; |
| |
| import com.android.locationtracker.data.TrackerDataHelper; |
| |
| import android.app.Service; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.SharedPreferences; |
| import android.content.SharedPreferences.OnSharedPreferenceChangeListener; |
| import android.location.Location; |
| import android.location.LocationListener; |
| import android.location.LocationManager; |
| import android.net.ConnectivityManager; |
| import android.net.wifi.ScanResult; |
| import android.net.wifi.WifiManager; |
| import android.os.Bundle; |
| import android.os.IBinder; |
| import android.preference.PreferenceManager; |
| import android.telephony.CellLocation; |
| import android.telephony.PhoneStateListener; |
| import android.telephony.SignalStrength; |
| import android.telephony.TelephonyManager; |
| import android.telephony.cdma.CdmaCellLocation; |
| import android.telephony.gsm.GsmCellLocation; |
| import android.util.Log; |
| import android.widget.Toast; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * Location Tracking service |
| * |
| * Records location updates for all registered location providers, and cell |
| * location updates |
| */ |
| public class TrackerService extends Service { |
| |
| private List<LocationTrackingListener> mListeners; |
| |
| private static final String LOG_TAG = TrackerActivity.LOG_TAG; |
| |
| // controls which location providers to track |
| private Set<String> mTrackedProviders; |
| |
| private TrackerDataHelper mTrackerData; |
| |
| private TelephonyManager mTelephonyManager; |
| private Location mNetworkLocation; |
| |
| // Handlers and Receivers for phone and network state |
| private NetworkStateBroadcastReceiver mNetwork; |
| private static final String CELL_PROVIDER_TAG = "cell"; |
| // signal strength updates |
| private static final String SIGNAL_PROVIDER_TAG = "signal"; |
| private static final String WIFI_PROVIDER_TAG = "wifi"; |
| // tracking tag for data connectivity issues |
| private static final String DATA_CONN_PROVIDER_TAG = "data"; |
| |
| // preference constants |
| private static final String MIN_TIME_PREF = "mintime_preference"; |
| private static final String MIN_DIS_PREF = "mindistance_preference"; |
| private static final String GPS_PREF = "gps_preference"; |
| private static final String NETWORK_PREF = "network_preference"; |
| private static final String SIGNAL_PREF = "signal_preference"; |
| private static final String DEBUG_PREF = "advanced_log_preference"; |
| |
| private PreferenceListener mPrefListener; |
| |
| public TrackerService() { |
| } |
| |
| @Override |
| public IBinder onBind(Intent intent) { |
| // ignore - nothing to do |
| return null; |
| } |
| |
| /** |
| * registers location listeners |
| * |
| * @param intent |
| * @param startId |
| */ |
| @Override |
| public void onStart(Intent intent, int startId) { |
| super.onStart(intent, startId); |
| mNetworkLocation = null; |
| |
| initLocationListeners(); |
| Toast.makeText(this, "Tracking service started", Toast.LENGTH_SHORT); |
| } |
| |
| private synchronized void initLocationListeners() { |
| mTrackerData = new TrackerDataHelper(this); |
| LocationManager lm = getLocationManager(); |
| |
| mTrackedProviders = getTrackedProviders(); |
| |
| List<String> locationProviders = lm.getAllProviders(); |
| mListeners = new ArrayList<LocationTrackingListener>( |
| locationProviders.size()); |
| |
| long minUpdateTime = getLocationUpdateTime(); |
| float minDistance = getLocationMinDistance(); |
| |
| for (String providerName : locationProviders) { |
| if (mTrackedProviders.contains(providerName)) { |
| Log.d(LOG_TAG, "Adding location listener for provider " + |
| providerName); |
| if (doDebugLogging()) { |
| mTrackerData.writeEntry("init", String.format( |
| "start listening to %s : %d ms; %f meters", |
| providerName, minUpdateTime, minDistance)); |
| } |
| LocationTrackingListener listener = |
| new LocationTrackingListener(); |
| lm.requestLocationUpdates(providerName, minUpdateTime, |
| minDistance, listener); |
| mListeners.add(listener); |
| } |
| } |
| mTelephonyManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE); |
| |
| if (doDebugLogging()) { |
| // register for cell location updates |
| mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CELL_LOCATION); |
| |
| // Register for Network (Wifi or Mobile) updates |
| mNetwork = new NetworkStateBroadcastReceiver(); |
| IntentFilter mIntentFilter; |
| mIntentFilter = new IntentFilter(); |
| mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); |
| mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); |
| mIntentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); |
| Log.d(LOG_TAG, "registering receiver"); |
| registerReceiver(mNetwork, mIntentFilter); |
| } |
| |
| if (trackSignalStrength()) { |
| mTelephonyManager.listen(mPhoneStateListener, |
| PhoneStateListener.LISTEN_SIGNAL_STRENGTHS); |
| } |
| |
| // register for preference changes, so we can restart listeners on |
| // pref changes |
| mPrefListener = new PreferenceListener(); |
| getPreferences().registerOnSharedPreferenceChangeListener(mPrefListener); |
| } |
| |
| private Set<String> getTrackedProviders() { |
| Set<String> providerSet = new HashSet<String>(); |
| |
| if (trackGPS()) { |
| providerSet.add(LocationManager.GPS_PROVIDER); |
| } |
| if (trackNetwork()) { |
| providerSet.add(LocationManager.NETWORK_PROVIDER); |
| } |
| return providerSet; |
| } |
| |
| private SharedPreferences getPreferences() { |
| return PreferenceManager.getDefaultSharedPreferences(this); |
| } |
| |
| private boolean trackNetwork() { |
| return getPreferences().getBoolean(NETWORK_PREF, true); |
| } |
| |
| private boolean trackGPS() { |
| return getPreferences().getBoolean(GPS_PREF, true); |
| } |
| |
| private boolean doDebugLogging() { |
| return getPreferences().getBoolean(DEBUG_PREF, false); |
| } |
| |
| private boolean trackSignalStrength() { |
| return getPreferences().getBoolean(SIGNAL_PREF, false); |
| } |
| |
| private float getLocationMinDistance() { |
| try { |
| String disString = getPreferences().getString(MIN_DIS_PREF, "0"); |
| return Float.parseFloat(disString); |
| } |
| catch (NumberFormatException e) { |
| Log.e(LOG_TAG, "Invalid preference for location min distance", e); |
| } |
| return 0; |
| } |
| |
| private long getLocationUpdateTime() { |
| try { |
| String timeString = getPreferences().getString(MIN_TIME_PREF, "0"); |
| long secondsTime = Long.valueOf(timeString); |
| return secondsTime * 1000; |
| } |
| catch (NumberFormatException e) { |
| Log.e(LOG_TAG, "Invalid preference for location min time", e); |
| } |
| return 0; |
| } |
| |
| /** |
| * Shuts down this service |
| */ |
| @Override |
| public void onDestroy() { |
| super.onDestroy(); |
| Log.d(LOG_TAG, "Removing location listeners"); |
| stopListeners(); |
| Toast.makeText(this, "Tracking service stopped", Toast.LENGTH_SHORT); |
| } |
| |
| /** |
| * De-registers all location listeners, closes persistent storage |
| */ |
| protected synchronized void stopListeners() { |
| LocationManager lm = getLocationManager(); |
| if (mListeners != null) { |
| for (LocationTrackingListener listener : mListeners) { |
| lm.removeUpdates(listener); |
| } |
| mListeners.clear(); |
| } |
| mListeners = null; |
| |
| // stop cell state listener |
| if (mTelephonyManager != null) { |
| mTelephonyManager.listen(mPhoneStateListener, 0); |
| } |
| |
| // stop network/wifi listener |
| if (mNetwork != null) { |
| unregisterReceiver(mNetwork); |
| } |
| mNetwork = null; |
| |
| mTrackerData = null; |
| if (mPrefListener != null) { |
| getPreferences().unregisterOnSharedPreferenceChangeListener(mPrefListener); |
| mPrefListener = null; |
| } |
| } |
| |
| private LocationManager getLocationManager() { |
| return (LocationManager) getSystemService(Context.LOCATION_SERVICE); |
| } |
| |
| /** |
| * Determine the current distance from given location to the last |
| * approximated network location |
| * |
| * @param location - new location |
| * |
| * @return float distance in meters |
| */ |
| private synchronized float getDistanceFromNetwork(Location location) { |
| float value = 0; |
| if (mNetworkLocation != null) { |
| value = location.distanceTo(mNetworkLocation); |
| } |
| if (LocationManager.NETWORK_PROVIDER.equals(location.getProvider())) { |
| mNetworkLocation = location; |
| } |
| return value; |
| } |
| |
| private class LocationTrackingListener implements LocationListener { |
| |
| /** |
| * Writes details of location update to tracking file, including |
| * recording the distance between this location update and the last |
| * network location update |
| * |
| * @param location - new location |
| */ |
| public void onLocationChanged(Location location) { |
| if (location == null) { |
| return; |
| } |
| float distance = getDistanceFromNetwork(location); |
| mTrackerData.writeEntry(location, distance); |
| } |
| |
| /** |
| * Writes update to tracking file |
| * |
| * @param provider - name of disabled provider |
| */ |
| public void onProviderDisabled(String provider) { |
| if (doDebugLogging()) { |
| mTrackerData.writeEntry(provider, "provider disabled"); |
| } |
| } |
| |
| /** |
| * Writes update to tracking file |
| * |
| * @param provider - name of enabled provider |
| */ |
| public void onProviderEnabled(String provider) { |
| if (doDebugLogging()) { |
| mTrackerData.writeEntry(provider, "provider enabled"); |
| } |
| } |
| |
| /** |
| * Writes update to tracking file |
| * |
| * @param provider - name of provider whose status changed |
| * @param status - new status |
| * @param extras - optional set of extra status messages |
| */ |
| public void onStatusChanged(String provider, int status, Bundle extras) { |
| if (doDebugLogging()) { |
| mTrackerData.writeEntry(provider, "status change: " + status); |
| } |
| } |
| } |
| |
| PhoneStateListener mPhoneStateListener = new PhoneStateListener() { |
| @Override |
| public void onCellLocationChanged(CellLocation location) { |
| try { |
| if (location instanceof GsmCellLocation) { |
| GsmCellLocation cellLocation = (GsmCellLocation)location; |
| String updateMsg = "cid=" + cellLocation.getCid() + |
| ", lac=" + cellLocation.getLac(); |
| mTrackerData.writeEntry(CELL_PROVIDER_TAG, updateMsg); |
| } else if (location instanceof CdmaCellLocation) { |
| CdmaCellLocation cellLocation = (CdmaCellLocation)location; |
| String updateMsg = "BID=" + cellLocation.getBaseStationId() + |
| ", SID=" + cellLocation.getSystemId() + |
| ", NID=" + cellLocation.getNetworkId() + |
| ", lat=" + cellLocation.getBaseStationLatitude() + |
| ", long=" + cellLocation.getBaseStationLongitude() + |
| ", SID=" + cellLocation.getSystemId() + |
| ", NID=" + cellLocation.getNetworkId(); |
| mTrackerData.writeEntry(CELL_PROVIDER_TAG, updateMsg); |
| } |
| } catch (Exception e) { |
| Log.e(LOG_TAG, "Exception in CellStateHandler.handleMessage:", e); |
| } |
| } |
| |
| public void onSignalStrengthsChanged(SignalStrength signalStrength) { |
| if (mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { |
| String updateMsg = "cdma dBM=" + signalStrength.getCdmaDbm(); |
| mTrackerData.writeEntry(SIGNAL_PROVIDER_TAG, updateMsg); |
| } else if (mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) { |
| String updateMsg = "gsm signal=" + signalStrength.getGsmSignalStrength(); |
| mTrackerData.writeEntry(SIGNAL_PROVIDER_TAG, updateMsg); |
| } |
| } |
| }; |
| |
| /** |
| * Listener + recorder for mobile or wifi updates |
| */ |
| private class NetworkStateBroadcastReceiver extends BroadcastReceiver { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| String action = intent.getAction(); |
| |
| if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { |
| WifiManager wifiManager = |
| (WifiManager) context.getSystemService(Context.WIFI_SERVICE); |
| List<ScanResult> wifiScanResults = wifiManager.getScanResults(); |
| String updateMsg = "num scan results=" + |
| (wifiScanResults == null ? "0" : wifiScanResults.size()); |
| mTrackerData.writeEntry(WIFI_PROVIDER_TAG, updateMsg); |
| |
| } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) { |
| String updateMsg; |
| boolean noConnectivity = |
| intent.getBooleanExtra( |
| ConnectivityManager.EXTRA_NO_CONNECTIVITY, false); |
| if (noConnectivity) { |
| updateMsg = "no connectivity"; |
| } |
| else { |
| updateMsg = "connection available"; |
| } |
| mTrackerData.writeEntry(DATA_CONN_PROVIDER_TAG, updateMsg); |
| |
| } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { |
| int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, |
| WifiManager.WIFI_STATE_UNKNOWN); |
| |
| String stateString = "unknown"; |
| switch (state) { |
| case WifiManager.WIFI_STATE_DISABLED: |
| stateString = "disabled"; |
| break; |
| case WifiManager.WIFI_STATE_DISABLING: |
| stateString = "disabling"; |
| break; |
| case WifiManager.WIFI_STATE_ENABLED: |
| stateString = "enabled"; |
| break; |
| case WifiManager.WIFI_STATE_ENABLING: |
| stateString = "enabling"; |
| break; |
| } |
| mTrackerData.writeEntry(WIFI_PROVIDER_TAG, |
| "state = " + stateString); |
| } |
| } |
| } |
| |
| private class PreferenceListener implements OnSharedPreferenceChangeListener { |
| |
| public void onSharedPreferenceChanged( |
| SharedPreferences sharedPreferences, String key) { |
| Log.d(LOG_TAG, "restarting listeners due to preference change"); |
| synchronized (TrackerService.this) { |
| stopListeners(); |
| initLocationListeners(); |
| } |
| } |
| } |
| } |