package com.ianhanniballake.contractiontimer; import android.content.AsyncQueryHandler; import android.content.ContentUris; import android.content.ContentValues; import android.content.SharedPreferences; import android.database.ContentObserver; import android.database.Cursor; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Build; import android.os.Handler; import android.preference.PreferenceManager; import android.provider.BaseColumns; import android.service.quicksettings.Tile; import android.service.quicksettings.TileService; import android.support.annotation.RequiresApi; import android.text.format.DateUtils; import android.util.Log; import com.google.firebase.analytics.FirebaseAnalytics; import com.ianhanniballake.contractiontimer.appwidget.AppWidgetUpdateHandler; import com.ianhanniballake.contractiontimer.notification.NotificationUpdateService; import com.ianhanniballake.contractiontimer.provider.ContractionContract; import com.ianhanniballake.contractiontimer.ui.Preferences; @RequiresApi(api = Build.VERSION_CODES.N) public class QuickTileService extends TileService { private static final String TAG = QuickTileService.class.getSimpleName(); private ContentObserver observer; private AsyncQueryHandler contractionQueryHandler; private boolean contractionOngoing; private long latestContractionId; @Override public void onCreate() { super.onCreate(); observer = new ContentObserver(new Handler()) { @Override public void onChange(final boolean selfChange, final Uri uri) { updateTile(); } }; contractionQueryHandler = new AsyncQueryHandler(getContentResolver()) { @Override protected void onInsertComplete(final int token, final Object cookie, final Uri uri) { AppWidgetUpdateHandler.createInstance().updateAllWidgets(QuickTileService.this); NotificationUpdateService.updateNotification(QuickTileService.this); } @Override protected void onUpdateComplete(final int token, final Object cookie, final int result) { AppWidgetUpdateHandler.createInstance().updateAllWidgets(QuickTileService.this); NotificationUpdateService.updateNotification(QuickTileService.this); } }; } @Override public void onStartListening() { getContentResolver().registerContentObserver(ContractionContract.Contractions.CONTENT_URI, true, observer); updateTile(); } private void updateTile() { final String[] projection = {BaseColumns._ID, ContractionContract.Contractions.COLUMN_NAME_START_TIME, ContractionContract.Contractions.COLUMN_NAME_END_TIME}; final String selection = ContractionContract.Contractions.COLUMN_NAME_START_TIME + ">?"; final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); final long averagesTimeFrame = Long.parseLong(preferences.getString( Preferences.AVERAGE_TIME_FRAME_PREFERENCE_KEY, getString(R.string.pref_average_time_frame_default))); final long timeCutoff = System.currentTimeMillis() - averagesTimeFrame; final String[] selectionArgs = {Long.toString(timeCutoff)}; final Cursor data = getContentResolver().query(ContractionContract.Contractions.CONTENT_URI, projection, selection, selectionArgs, null); // Get the average duration and frequency String averages = null; if (data != null && data.moveToFirst()) { double averageDuration = 0; double averageFrequency = 0; int numDurations = 0; int numFrequencies = 0; while (!data.isAfterLast()) { final int startTimeColumnIndex = data .getColumnIndex(ContractionContract.Contractions.COLUMN_NAME_START_TIME); final long startTime = data.getLong(startTimeColumnIndex); final int endTimeColumnIndex = data .getColumnIndex(ContractionContract.Contractions.COLUMN_NAME_END_TIME); if (!data.isNull(endTimeColumnIndex)) { final long endTime = data.getLong(endTimeColumnIndex); final long curDuration = endTime - startTime; averageDuration = (curDuration + numDurations * averageDuration) / (numDurations + 1); numDurations++; } if (data.moveToNext()) { final int prevContractionStartTimeColumnIndex = data .getColumnIndex(ContractionContract.Contractions.COLUMN_NAME_START_TIME); final long prevContractionStartTime = data.getLong(prevContractionStartTimeColumnIndex); final long curFrequency = startTime - prevContractionStartTime; averageFrequency = (curFrequency + numFrequencies * averageFrequency) / (numFrequencies + 1); numFrequencies++; } } final long averageDurationInSeconds = (long) (averageDuration / 1000); String formattedAverageDuration = DateUtils.formatElapsedTime(averageDurationInSeconds); final long averageFrequencyInSeconds = (long) (averageFrequency / 1000); String formattedAverageFrequency = DateUtils.formatElapsedTime(averageFrequencyInSeconds); averages = getString(R.string.tile_label, formattedAverageDuration, formattedAverageFrequency); } // Set the status of the contraction toggle button // Need to use a separate cursor as there could be running contractions // outside of the average time frame final Cursor allData = getContentResolver().query(ContractionContract.Contractions.CONTENT_URI, projection, null, null, null); contractionOngoing = allData != null && allData.moveToFirst() && allData.isNull(allData.getColumnIndex(ContractionContract.Contractions.COLUMN_NAME_END_TIME)); latestContractionId = contractionOngoing ? allData.getLong(allData.getColumnIndex(BaseColumns._ID)) : 0; // Close the cursors if (data != null) data.close(); if (allData != null) allData.close(); Tile tile = getQsTile(); String backupLabel; if (contractionOngoing) { tile.setState(Tile.STATE_ACTIVE); tile.setIcon(Icon.createWithResource(this, R.drawable.ic_tile_stop)); tile.setContentDescription(getString(R.string.appwidget_contraction_stop)); backupLabel = getString(R.string.notification_timing); } else { tile.setState(Tile.STATE_INACTIVE); tile.setIcon(Icon.createWithResource(this, R.drawable.ic_tile_start)); tile.setContentDescription(getString(R.string.appwidget_contraction_start)); backupLabel = getString(R.string.app_name); } tile.setLabel(averages != null ? averages : backupLabel); tile.updateTile(); } @Override public void onClick() { FirebaseAnalytics analytics = FirebaseAnalytics.getInstance(this); if (!contractionOngoing) { if (BuildConfig.DEBUG) Log.d(TAG, "Starting contraction"); analytics.logEvent("quick_tile_start", null); // Start a new contraction contractionQueryHandler.startInsert(0, null, ContractionContract.Contractions.CONTENT_URI, new ContentValues()); } else { if (BuildConfig.DEBUG) Log.d(TAG, "Stopping contraction"); analytics.logEvent("quick_tile_stop", null); final ContentValues newEndTime = new ContentValues(); newEndTime.put(ContractionContract.Contractions.COLUMN_NAME_END_TIME, System.currentTimeMillis()); final Uri updateUri = ContentUris.withAppendedId( ContractionContract.Contractions.CONTENT_ID_URI_BASE, latestContractionId); // Add the new end time to the last contraction contractionQueryHandler.startUpdate(0, 0, updateUri, newEndTime, null, null); } } @Override public void onStopListening() { getContentResolver().unregisterContentObserver(observer); } }