package com.darkrockstudios.apps.tminus; import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.AsyncTask; import android.os.IBinder; import android.os.PowerManager; import android.preference.PreferenceManager; import android.util.Log; import com.android.volley.Response; import com.android.volley.VolleyError; import com.android.volley.toolbox.JsonObjectRequest; import com.darkrockstudios.apps.tminus.database.DatabaseHelper; import com.darkrockstudios.apps.tminus.database.DatabaseUtilities; import com.darkrockstudios.apps.tminus.launchlibrary.Launch; import com.darkrockstudios.apps.tminus.launchlibrary.LaunchLibraryGson; import com.darkrockstudios.apps.tminus.launchlibrary.LaunchLibraryUrls; import com.darkrockstudios.apps.tminus.launchlibrary.Mission; import com.darkrockstudios.apps.tminus.launchlibrary.Pad; import com.darkrockstudios.apps.tminus.misc.Preferences; import com.google.gson.Gson; import com.google.gson.JsonParseException; import com.j256.ormlite.android.apptools.OpenHelperManager; import com.j256.ormlite.dao.Dao; import com.j256.ormlite.misc.TransactionManager; import com.j256.ormlite.stmt.DeleteBuilder; import org.joda.time.DateTime; import org.joda.time.Duration; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.sql.SQLException; import java.util.Date; import java.util.concurrent.Callable; /** * Created by Adam on 7/13/13. * Dark Rock Studios * darkrockstudios.com */ public class LaunchUpdateService extends Service { private static final String TAG = LaunchUpdateService.class.getSimpleName(); public static final String ACTION_LAUNCH_LIST_UPDATED = LaunchUpdateService.class.getPackage() + ".ACTION_LAUNCH_LIST_UPDATED"; public static final String ACTION_LAUNCH_LIST_UPDATE_FAILED = LaunchUpdateService.class.getPackage() + ".ACTION_LAUNCH_LIST_UPDATE_FAILED"; public static final String EXTRA_REQUEST_PREVIOUS_LAUNCHES = LaunchUpdateService.class.getPackage() + ".REQUEST_PREVIOUS_LAUNCHES"; private static final int UPCOMING_LAUNCH_REQUEST_COUNT = 20; private static final int PREVIOUS_LAUNCH_REQUEST_COUNT = 10; private PowerManager.WakeLock m_wakeLock; public LaunchUpdateService() { super(); } @Override public void onCreate() { super.onCreate(); Log.d( TAG, "LaunchUpdateService created." ); final PowerManager powerManager = (PowerManager) getSystemService( Context.POWER_SERVICE ); m_wakeLock = powerManager.newWakeLock( PowerManager.PARTIAL_WAKE_LOCK, TAG ); } @Override public void onDestroy() { super.onDestroy(); Log.d( TAG, "LaunchUpdateService destroyed." ); if( m_wakeLock.isHeld() ) { m_wakeLock.release(); } } @Override public int onStartCommand( final Intent intent, final int flags, final int startId ) { Log.d( TAG, "LaunchUpdateService started." ); final boolean previousLaunches; if( intent != null && intent.hasExtra( EXTRA_REQUEST_PREVIOUS_LAUNCHES ) ) { previousLaunches = intent.getBooleanExtra( EXTRA_REQUEST_PREVIOUS_LAUNCHES, false ); } else { previousLaunches = false; } requestLaunches( previousLaunches ); return START_NOT_STICKY; } @Override public IBinder onBind( final Intent intent ) { return null; } private void requestLaunches( final boolean previousLaunches ) { Log.d( TAG, "Requesting Launches..." ); if( !m_wakeLock.isHeld() ) { m_wakeLock.acquire(); Log.d( TAG, "WakeLock acquired." ); } final String url; if( !previousLaunches ) { url = LaunchLibraryUrls.next( UPCOMING_LAUNCH_REQUEST_COUNT ); } else { url = LaunchLibraryUrls.last( PREVIOUS_LAUNCH_REQUEST_COUNT ); } LaunchListResponseListener listener = new LaunchListResponseListener( previousLaunches ); JsonObjectRequest request = new JsonObjectRequest( url, null, listener, listener ); request.setTag( this ); TMinusApplication.getRequestQueue().add( request ); } private void stopService() { TMinusApplication.getRequestQueue().cancelAll( this ); if( m_wakeLock.isHeld() ) { m_wakeLock.release(); } stopSelf(); } private void sendSuccessBroadcast() { Log.i( TAG, "Launches successfully updates, sending success broadcast." ); final Intent intent = new Intent( ACTION_LAUNCH_LIST_UPDATED ); sendBroadcast( intent ); } private void sendFailureBroadcast() { Log.i( TAG, "Launches update failed, sending failure broadcast." ); final Intent intent = new Intent( ACTION_LAUNCH_LIST_UPDATE_FAILED ); sendBroadcast( intent ); } private class LaunchListResponseListener implements Response.Listener<JSONObject>, Response.ErrorListener { private final boolean m_previousLaunches; public LaunchListResponseListener( final boolean previousLaunches ) { m_previousLaunches = previousLaunches; } @Override public void onResponse( final JSONObject response ) { Log.i( TAG, "Launches successfully retrieved from sever." ); LaunchListSaver loader = new LaunchListSaver( m_previousLaunches ); loader.execute( response ); } @Override public void onErrorResponse( final VolleyError error ) { Log.i( TAG, "Failed to retrieve Launches from sever." ); String errorMessage = error.getMessage(); if( errorMessage != null ) { Log.i( TAG, "VolleyError: " + errorMessage ); } sendFailureBroadcast(); stopService(); } } private class LaunchListSaver extends AsyncTask<JSONObject, Void, Long> { private final boolean m_previousLaunches; public LaunchListSaver( final boolean previousLaunches ) { super(); m_previousLaunches = previousLaunches; } @Override protected Long doInBackground( final JSONObject... response ) { Log.d( TAG, "Beginning background processing of new Launches..." ); long numLaunches = 0; final Gson gson = LaunchLibraryGson.create(); final JSONObject launchListObj = response[ 0 ]; final DatabaseHelper databaseHelper = OpenHelperManager .getHelper( LaunchUpdateService.this, DatabaseHelper.class ); if( databaseHelper != null ) { try { final Dao<Launch, Integer> launchDao = databaseHelper.getDao( Launch.class ); final Dao<Mission, Integer> missionDao = databaseHelper.getDao( Mission.class ); final JSONArray launchListArray = launchListObj.getJSONArray( "launches" ); for( int ii = 0; ii < launchListArray.length(); ++ii ) { try { final JSONObject launchObj = launchListArray.getJSONObject( ii ); if( launchObj != null ) { final Launch launch = parseLaunch( launchObj, gson ); try { TransactionManager.callInTransaction( databaseHelper.getConnectionSource(), new Callable<Void>() { public Void call() throws Exception { // If the launch already exists, cancel any alarms for it if( launchDao.idExists( launchDao .extractId( launch ) ) ) { UpdateAlarmsService .cancelAlarmsForLaunch( launch, LaunchUpdateService.this ); } DatabaseUtilities .saveRocket( launch.rocket, databaseHelper ); DatabaseUtilities .saveLocation( launch.location, databaseHelper ); DeleteBuilder<Mission, Integer> missionDeleteBuilder = missionDao.deleteBuilder(); missionDeleteBuilder.where().eq( "launch_id", launch.id ); missionDeleteBuilder.delete(); if( launch.missions != null ) { for( final Mission mission : launch.missions ) { missionDao.createOrUpdate( mission ); } } // This must be run after all the others are created so the IDs of the child objects can be set launchDao.createOrUpdate( launch ); return null; } } ); } catch( final SQLException e ) { Log.w( TAG, e.getMessage() ); } } } catch( final JsonParseException e ) { e.printStackTrace(); } } Log.d( TAG, "Parsing and database work complete" ); // Only do this work for upcoming launches if( !m_previousLaunches ) { Log.d( TAG, "Launching AlarmUpdateService..." ); // Now that we have new data, ensure our Alarms are set correctly startService( new Intent( LaunchUpdateService.this, UpdateAlarmsService.class ) ); final SharedPreferences preferences = PreferenceManager .getDefaultSharedPreferences( LaunchUpdateService.this ); preferences.edit().putLong( Preferences.KEY_LAST_UPDATED, new Date().getTime() ).commit(); Log.d( TAG, "Refresh successful: " + numLaunches + " Launches in database." ); } numLaunches = launchDao.countOf(); } catch( final SQLException | JSONException e ) { e.printStackTrace(); } finally { OpenHelperManager.releaseHelper(); } } cleanUpOldLaunches(); return numLaunches; } private Launch parseLaunch( final JSONObject launchObj, final Gson gson ) throws JSONException { final Launch launch = gson.fromJson( launchObj.toString(), Launch.class ); // We need to hook up the parent child relationship for the database if( launch.missions != null && launch.missions.size() > 0 ) { for( final Mission mission : launch.missions ) { mission.launch = launch; } } // We need to hook up the parent child relationship for the database for( final Pad pad : launch.location.pads ) { pad.location = launch.location; } return launch; } @Override protected void onPostExecute( final Long result ) { Log.d( TAG, "Background update complete." ); sendSuccessBroadcast(); stopService(); } private DateTime getOldLaunchThreshold() { final long MAX_DAYS_OLD = 365; return DateTime.now().minus( Duration.standardDays( MAX_DAYS_OLD ) ); } private void cleanUpOldLaunches() { final DatabaseHelper databaseHelper = OpenHelperManager .getHelper( LaunchUpdateService.this, DatabaseHelper.class ); if( databaseHelper != null ) { try { final Dao<Launch, Integer> launchDao = databaseHelper.getDao( Launch.class ); DeleteBuilder<Launch, Integer> builder = launchDao.deleteBuilder(); builder.where().lt( "net", getOldLaunchThreshold() ); launchDao.delete( builder.prepare() ); } catch( final SQLException e ) { e.printStackTrace(); } finally { OpenHelperManager.releaseHelper(); } } } } }