Android Open Source - artifactly Artifactly Service






From Project

Back to project page artifactly.

License

The source code is released under:

Apache License

If you think the Android project artifactly listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * Copyright 2011 Thomas Amsler/*from   w  w w  .  ja va  2 s.  c  o m*/
 *
 * 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 org.artifactly.client.service;

import org.artifactly.client.ApplicationConstants;
import org.artifactly.client.Artifactly;
import org.artifactly.client.R;
import org.artifactly.client.content.DbAdapter;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.database.Cursor;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;

public class ArtifactlyService extends Service implements OnSharedPreferenceChangeListener, ApplicationConstants {

  // Logging
  //private static final String DEBUG_LOG_TAG = "** DEBUG A.S. **";
  private static final String PROD_LOG_TAG = "** A.S. **";

  // Preferences
  private static final String PREFS_NAME = "ArtifactlyPrefsFile";
  private SharedPreferences settings;

  // Location constants
  private static final long GPS_LOCATION_MIN_TIME = 300000;     // 5 min
  private static final float GPS_LOCATION_MIN_DISTANCE = 50.0f; // 50 m
  private static final long NET_LOCATION_MIN_TIME = 240000;     // 3 min
  private static final float NET_LOCATION_MIN_DISTANCE = 50.0f; // 50 m
  private static final long PAS_LOCATION_MIN_TIME = 30000;      // 30 sec
  private static final float PAS_LOCATION_MIN_DISTANCE = 50.0f; // 50 m
  protected static final String DISTANCE = "dist";
  private static final String GPS_PROVIDER = "GPS";
  private static final String NET_PROVIDER = "NET";
  private static final String PAS_PROVIDER = "PAS";

  // Location radius
  private int radius = PREFERENCE_RADIUS_DEFAULT;
  private String radiusUnit = PREFERENCE_RADIUS_UNIT_DEFAULT;
  
  // Sound Notification Preference
  private boolean soundNotificationPreference = PREFERENCE_SOUND_NOTIFICATION_DEFAULT;
  
  // Location expiration delta is used to determine if the current location
  // is current enough. If it's not, we enable the GPS listener if available 
  private static final long LOCATION_TIME_EXPIRATION_DELTA = 300000; // 5 min
  
  // The new location can only be older than the current location plus this delta
  // in order for it to be considered slightly inaccurate 
  private static final long LOCATION_TIME_ALLOWED_DELTA = 300000; // 5 min

  // Max allowed location accuracy delta
  private static final int LOCATION_MAX_ACCURACY_DELTA = 2000; // 2 km
  
  // Notification constants
  private static final int NOTIFICATION_ID = 95691;
  
  // Context Resources
  private String NOTIFICATION_TICKER_TEXT;
  private String NOTIFICATION_CONTENT_TITLE;
  private String NOTIFICATION_MESSAGE;

  // Initialization flag
  private boolean runInit = true;
  
  // Managers
  private LocationManager locationManager;
  private NotificationManager notificationManager;

  // Location provider
  private String mainLocationProviderName;
  
  // Location listeners
  private LocationListener gpsLocationListener;
  private LocationListener networkLocationListener;
  private LocationListener passiveLocationListener;
  
  // Location state
  private boolean isGpsListenerEnabled = false;
  
  // Last location update
  private long lastLocationUpdateTime = 0;
  
  // Last send notification time
  private long lastSendNotificationTime = 0;
  private long lastSendNotificationSoundTime = 0;
  private static final long LAST_SEND_NOTIFICATION_TIME_DELTA = 60000; // 1 min
  private static final long LAST_SOUND_NOTIFICATION_TIME_DELTA = 300000; // 5 min
  
  // DB adapter
  private DbAdapter dbAdapter;

  // Keeping track of current location
  private Location currentLocation;

  // Binder access to service API
  private IBinder localServiceBinder;

  // Intent for sending location update broadcast
  private Intent locationUpdateIntent = new Intent(LOCATION_UPDATE_INTENT);
  
  // Intent for sending has artifacts at current location broadcast
  private Intent hasArtifactsAtCurrentLocationIntent = new Intent(HAS_ARTIFACTS_AT_CURRENT_LOCATION_INTENT);
  
  public ArtifactlyService() {
    
    super();
  }
  
  /*
   * (non-Javadoc)
   * @see android.app.Service#onStartCommand(android.content.Intent, int, int)
   */
  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {

    // Initialize the service
    try {

      init();
    }
    catch(Exception e) {

      Log.w(PROD_LOG_TAG, "Exception occured", e);
      runInit = true;
    }

    return START_STICKY;
  }
  
  /*
   * (non-Javadoc)
   * @see android.app.Service#onBind(android.content.Intent)
   */
  @Override
  public IBinder onBind(Intent intent) {
    
    localServiceBinder = new LocalServiceImpl(this);
    return localServiceBinder;
  }

  /*
   * (non-Javadoc)
   * @see android.app.Service#onUnbind(android.content.Intent)
   */
  @Override
  public boolean onUnbind(Intent intent) {

    return super.onUnbind(intent);
  }
  
  /*
   * (non-Javadoc)
   * @see android.app.Service#onRebind(android.content.Intent)
   */
  @Override
  public void onRebind(Intent intent) {

    super.onRebind(intent);
  }
  
  /*
   * (non-Javadoc)
   * @see android.app.Service#onCreate()
   */
  @Override
  public void onCreate() {

    super.onCreate();

    // Initialize the service
    try {

      init();
    }
    catch(Exception e) {

      Log.w(PROD_LOG_TAG, "Exception occured", e);
      runInit = true;
    }
  }
  
  /*
   * (non-Javadoc)
   * @see android.app.Service#onDestroy()
   */
  @Override
  public void onDestroy() {
    
    super.onDestroy();
    
    settings.unregisterOnSharedPreferenceChangeListener(this);
  }

  /*
   * (non-Javadoc)
   * @see android.content.SharedPreferences.OnSharedPreferenceChangeListener#onSharedPreferenceChanged(android.content.SharedPreferences, java.lang.String)
   */
  public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {

    if(PREFERENCE_RADIUS.equals(key)) {
      
      int newRadius = sharedPreferences.getInt(key, PREFERENCE_RADIUS_DEFAULT);

      if(0 < newRadius) {
        
        radius = newRadius;
        sendBroadcast(locationUpdateIntent);
      }
    }
    else if(PREFERENCE_SOUND_NOTIFICATION.equals(key)) {
      
      soundNotificationPreference = sharedPreferences.getBoolean(key, PREFERENCE_SOUND_NOTIFICATION_DEFAULT);
    }
    else if(PREFERENCE_RADIUS_UNIT.equals(key)) {
      
      radiusUnit = sharedPreferences.getString(key, PREFERENCE_RADIUS_UNIT_DEFAULT);
    }
  }

  /*
   * Initialization 
   */
  private void init() {
    
    // Determine if we actually need to initialize
    synchronized(this) {
      
      if(!runInit) {

        return;
      }
      
      runInit = false;
    }
    
    // Getting the constants from resources
    NOTIFICATION_TICKER_TEXT = getResources().getString(R.string.notification_ticker_text);
    NOTIFICATION_CONTENT_TITLE = getResources().getString(R.string.notification_content_title);
    NOTIFICATION_MESSAGE = getResources().getString(R.string.notification_message);

    // Getting shared preferences such as search radius, etc.
    settings = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
    settings.registerOnSharedPreferenceChangeListener(this);
    radius = settings.getInt(PREFERENCE_RADIUS, PREFERENCE_RADIUS_DEFAULT);
    radiusUnit = settings.getString(PREFERENCE_RADIUS_UNIT, PREFERENCE_RADIUS_UNIT_DEFAULT);
    soundNotificationPreference = settings.getBoolean(PREFERENCE_SOUND_NOTIFICATION, PREFERENCE_SOUND_NOTIFICATION_DEFAULT);

    // Setting up the database
    dbAdapter = new DbAdapter(this);

    // Setting up the notification manager
    notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

    // Setting up the location manager
    locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

    // Register location listener and getting last known location
    registerLocationListener();
  }
  
  /*
   * DbAdater getter method
   */
  protected DbAdapter getDbAdapter() {
    
    return dbAdapter;
  }
  
  /*
   * Helper method
   */
  protected boolean isNearbyCurrentLocation(String lat, String lng) {
    
    float[] distanceResult = new float[1];

    if(null == currentLocation) {
      
      return false;
    }
    
    try {
    
      Location.distanceBetween(currentLocation.getLatitude(),
          currentLocation.getLongitude(),
          Double.parseDouble(lat),
          Double.parseDouble(lng), distanceResult);
    }
    catch(NumberFormatException  exception) {

      Log.e(PROD_LOG_TAG, "ERROR: Was not able to parse povided lat/lng to a Double", exception);
    }

    if(((int)distanceResult[0]) <= getRadiusInMeters()) {
      
      return true;
    }
    
    return false;
  }

  /*
   * Helper method that returns the defined radius in meters
   */
  protected int getRadiusInMeters() {
    
    if(UNIT_M.equals(radiusUnit)) {
      
      return radius;
    }
    else if(UNIT_KM.equals(radiusUnit)) {
      
      return radius * 1000;
      
    }
    else if(UNIT_FT.equals(radiusUnit)) {
      
      return (int)(radius * 0.3048f);
      
    }
    else if(UNIT_MI.equals(radiusUnit)) {
      
      return (int)(radius * 1609.344f);
    }
    
    Log.e(PROD_LOG_TAG, "ERROR: Radius unit not recoginzied");
    return radius;
  }
  
  /*
   * Dispatch method for local service
   */
  protected void startLocationTracking() {

    registerLocationListener();
  }

  /*
   * Dispatch method for local service
   */
  protected void stopLocationTracking() {

    unregisterLocationListeners();
  }
  
  /*
   * Dispatch method for local service
   */
  protected Location getLocation() {

    return currentLocation;
  }
  
  /*
   * Dispatch method for local service
   */
  protected long getLastLocationUpdateTime() {
    
    return lastLocationUpdateTime;
  }

  /*
   * Register location listener
   * 1. network
   * 2. gps
   * ... and passive provider
   * 
   * Also setting currentLocation via last known location
   */
  private void registerLocationListener() {
    
    // If location manager is null, we just return
    if(null == locationManager) {
      
      locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
      
      if(null == locationManager) {
        
        Log.e(PROD_LOG_TAG, "Was not able to get LocationManager instance via getSystemService()");
        return;
      }
    }

    try {

      // First, use network provided location if available
      if(locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {

        mainLocationProviderName = LocationManager.NETWORK_PROVIDER;
        networkLocationListener = getNewLocationListener(NET_PROVIDER);
        locationManager.requestLocationUpdates(mainLocationProviderName, NET_LOCATION_MIN_TIME, NET_LOCATION_MIN_DISTANCE, networkLocationListener);
        currentLocation = locationManager.getLastKnownLocation(mainLocationProviderName);
      }
      else if(locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {

        mainLocationProviderName = LocationManager.GPS_PROVIDER;
        gpsLocationListener = getNewLocationListener(GPS_PROVIDER);
        locationManager.requestLocationUpdates(mainLocationProviderName, GPS_LOCATION_MIN_TIME, GPS_LOCATION_MIN_DISTANCE, gpsLocationListener);
        currentLocation = locationManager.getLastKnownLocation(mainLocationProviderName);
      }
      
      // Also enable Passive provider
      if(locationManager.isProviderEnabled(LocationManager.PASSIVE_PROVIDER)) {
        
        passiveLocationListener = getNewLocationListener(PAS_PROVIDER);
        locationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, PAS_LOCATION_MIN_TIME, PAS_LOCATION_MIN_DISTANCE, passiveLocationListener);
      }
    }
    catch(IllegalArgumentException iae) {
      
      Log.w(PROD_LOG_TAG, "registerLocationListener() IllegalArgumentException", iae);
    }
    catch(SecurityException se) {
      
      Log.w(PROD_LOG_TAG, "registerLocationListener() SecurityException", se);

    }
    catch(RuntimeException re) {
      
      Log.w(PROD_LOG_TAG, "registerLocationListener() RuntimeException", re);
    }
  }
  
  /*
   * Unregister location listeners
   * 
   */
  private void unregisterLocationListeners() {
    
    if(null == locationManager) {
      
      return;
    }

    try {
    
      locationManager.removeUpdates(networkLocationListener);
      networkLocationListener = null;
      locationManager.removeUpdates(passiveLocationListener);
      passiveLocationListener = null;
    }
    catch(IllegalArgumentException iae) {
      
      Log.w(PROD_LOG_TAG, "IllegalArgumentException thrown while removing location listener updates", iae);
    }
    catch(RuntimeException re) {
      
      Log.w(PROD_LOG_TAG, "unregisterLocationListeners() RuntimeException", re);
    }
  }

  /*
   * This method sends a message using Android's notification manager.
   * It sets up an intent so that the UI can be launched from within the notification message.
   */
  private void sendNotification() {
    
    Intent notificationIntent = new Intent(this, Artifactly.class);
    notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
    PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    Notification notification = new Notification(R.drawable.artifactly_launcher, NOTIFICATION_TICKER_TEXT, System.currentTimeMillis());
    notification.flags |= Notification.FLAG_AUTO_CANCEL;
    
    // We only send notifications with default sound no more frequent than every LAST_SOUND_NOTIFICATION_TIME_DELTA minutes
    if(soundNotificationPreference && ((lastSendNotificationSoundTime + LAST_SOUND_NOTIFICATION_TIME_DELTA) <= System.currentTimeMillis())) {
      
      lastSendNotificationSoundTime = System.currentTimeMillis();
      notification.defaults |= Notification.DEFAULT_SOUND;
    }
    
    notification.setLatestEventInfo(getApplicationContext(), NOTIFICATION_CONTENT_TITLE, NOTIFICATION_MESSAGE, contentIntent);
    notificationManager.notify(NOTIFICATION_ID, notification);
  }
  
  /*
   * Method that removes all notifications
   */
  private void cancelNotificaiton() {
  
    notificationManager.cancel(NOTIFICATION_ID);
  }

  /*
   * Method that checks if there are any artifacts that are close to the current 
   * location. It uses the defined radius to determine the closeness
   */
  protected boolean hasArtifactsForCurrentLocation() {
    
    if(null == dbAdapter) {
      
      Log.w(PROD_LOG_TAG, "DB Adapter is null");
      return false;
    }
    
    // Getting all the locations
    Cursor cursor = dbAdapter.select();
    if(null == cursor) {
      
      Log.w(PROD_LOG_TAG, "Cursor is null");
      return false;
    }
    
    // Checking if the cursor set has any items
    boolean hasItems = cursor.moveToFirst();
    
    if(!hasItems) {
      
      cursor.close();
      return false;
    }

    // Determine the table column indexes 
    int longitudeColumnIndex = cursor.getColumnIndex(DbAdapter.LOC_FIELDS[DbAdapter.LOC_LONGITUDE]);
    int latitudeColumnIndex = cursor.getColumnIndex(DbAdapter.LOC_FIELDS[DbAdapter.LOC_LATITUDE]);

    /*
     *  Iterating over all result and calculate the distance between
     *  radius, we notify the user that there is an artifact.
     */
    for(;cursor.isAfterLast() == false; cursor.moveToNext()) {

      // Getting latitude and longitude
      String lat = cursor.getString(latitudeColumnIndex).trim();
      String lng = cursor.getString(longitudeColumnIndex).trim();

      if(isNearbyCurrentLocation(lat, lng)) {

        cursor.close();
        return true;
      }
    }

    cursor.close();

    return false;
  }
    
  /*
   * Method that determines if a new location is more accurate the the currently saved location
   */
  private boolean isMoreAccurate(Location newLocation) {

    if(null == currentLocation && null != newLocation) {
      
      return true;
    }

    if(null != currentLocation && null == newLocation) {
    
      return false;
    }
    
    // Check if the new location's accuracy lies within the defined search radius
    if(newLocation.hasAccuracy() &&
       ((int)newLocation.getAccuracy()) > getRadiusInMeters() &&
       ((int)newLocation.getAccuracy()) > LOCATION_MAX_ACCURACY_DELTA) {
      
      return false;
    }
    
    boolean isMoreAccurate = false;
    
    // Check if the new location is more accurate 
    if(currentLocation.hasAccuracy() && newLocation.hasAccuracy()) {

      float accuracyDelta = currentLocation.getAccuracy() - newLocation.getAccuracy();
      isMoreAccurate = accuracyDelta >= 0;
    }
    
    // Check if the new location is more current in terms of location fix time
    long locationTimeDelta = currentLocation.getTime() - newLocation.getTime();
    boolean isMoreCurrent = locationTimeDelta <= 0;
    
    if(isMoreAccurate && isMoreCurrent) {
      
      return true;
    }
    
    boolean isSlightlyLessCurrent = (locationTimeDelta > 0 && locationTimeDelta < LOCATION_TIME_ALLOWED_DELTA);
    
    if(isMoreAccurate && isSlightlyLessCurrent) {
      
      return true;
    }
    
    boolean isSlightlyLessAccurate = (newLocation.hasAccuracy() && newLocation.getAccuracy() <= LOCATION_MAX_ACCURACY_DELTA);
    if(isSlightlyLessAccurate && isMoreCurrent) {
      
      return true;
    }
    
    return false;
  }
  
  
  /*
   * Create a location listener
   */
  private LocationListener getNewLocationListener(final String listenerName) {
    
    return new LocationListener() {

      public void onLocationChanged(final Location location) {
        
        locationChanged(location);
      }

      public void onProviderDisabled(String provider) {
        
        locationProviderDisabled(provider);
      }

      public void onProviderEnabled(String provider) {
        
        locationProviderEnabled(provider);
      }

      public void onStatusChanged(String provider, int status, Bundle extras) {
        
        locationStatusChanged(provider, status, extras);
      }
    };
  }
  
  /*
   * (non-Javadoc)
   * @see android.location.LocationListener#onLocationChanged(android.location.Location)
   */
  private void locationChanged(Location location) {

    // Don't handle consecutive location changes. e.g. PASSIVE provider
    if(null != currentLocation && Float.compare(currentLocation.distanceTo(location), 0.0f) == 0) {
      
      return;
    }
    
    lastLocationUpdateTime = System.currentTimeMillis();

    // First we check if the new location is more accurate
    if(isMoreAccurate(location)) {
      
      // Update the current location with the new one
      currentLocation = location;

      // Sending a location update broadcast to client 
      sendBroadcast(locationUpdateIntent);

      // Since we are getting a more accurate location, we should check if the 
      // GPS listener is still enabled. If it is enabled we can turn it off
      if(isGpsListenerEnabled) {
        
        try {
          
          locationManager.removeUpdates(gpsLocationListener);
          gpsLocationListener = null;
          isGpsListenerEnabled = false;
        }
        catch(IllegalArgumentException iae) {
          
          isGpsListenerEnabled = false;
          Log.w(PROD_LOG_TAG, "Was not able to remove GPS listener updates", iae);
        }
      }
    
      // Make sure that we don't send too many notifications
      boolean canSendNotificaiton = ((lastSendNotificationTime + LAST_SEND_NOTIFICATION_TIME_DELTA) <= System.currentTimeMillis()) ? true : false;
      
      /*
       * Check if there are any artifacts close to the current location. If there are,
       * we send a notification.
       * 
       */
      if(canSendNotificaiton && hasArtifactsForCurrentLocation()) {

        lastSendNotificationTime = System.currentTimeMillis();
        sendNotification();
        sendBroadcast(hasArtifactsAtCurrentLocationIntent);
      }
      else {
        
        // Clear any old notifications
        cancelNotificaiton();
      }
    }
    else {
      
      // Check if the currentLocation has been updated recently
      long expirationTime = System.currentTimeMillis() - currentLocation.getTime() - LOCATION_TIME_EXPIRATION_DELTA;

      if(expirationTime > 0) {
        
        // The current location's fix time is too old. Check if GPS provider is 
        // available and try to get a better fix from it
        if(locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) && !isGpsListenerEnabled) {

          try {
            
            isGpsListenerEnabled = true;
            gpsLocationListener = getNewLocationListener(GPS_PROVIDER);
            locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, GPS_LOCATION_MIN_TIME, GPS_LOCATION_MIN_DISTANCE, gpsLocationListener);
          }
          catch(Exception e) {
            
            isGpsListenerEnabled = false;
            Log.w(PROD_LOG_TAG, "Was not able to start GPS listener", e);
          }
        }
      }
    }  
  }

  /*
   * (non-Javadoc)
   * @see android.location.LocationListener#onProviderDisabled(java.lang.String)
   */
  private void locationProviderDisabled(String provider) {

  }

  /*
   * (non-Javadoc)
   * @see android.location.LocationListener#onProviderEnabled(java.lang.String)
   */
  private void locationProviderEnabled(String provider) {

  }

  /*
   * (non-Javadoc)
   * @see android.location.LocationListener#onStatusChanged(java.lang.String, int, android.os.Bundle)
   */
  private void locationStatusChanged(String provider, int status, Bundle extras) {

  }
}




Java Source Code List

org.artifactly.client.ApplicationConstants.java
org.artifactly.client.Artifactly.java
org.artifactly.client.content.DbAdapter.java
org.artifactly.client.service.ArtifactlyService.java
org.artifactly.client.service.LocalServiceImpl.java
org.artifactly.client.service.LocalService.java
org.artifactly.client.service.StartServiceAtBoot.java