package ca.josephroque.bowlingcompanion; import com.google.android.gms.ads.AdListener; import com.google.android.gms.ads.AdRequest; import com.google.android.gms.ads.AdView; import android.app.ActivityManager; import android.app.AlertDialog; import android.content.ContentValues; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.ColorDrawable; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.util.Log; import android.util.TypedValue; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.widget.NumberPicker; import android.widget.TextView; import java.lang.ref.WeakReference; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; import ca.josephroque.bowlingcompanion.adapter.NavigationDrawerAdapter; import ca.josephroque.bowlingcompanion.database.Contract.FrameEntry; import ca.josephroque.bowlingcompanion.database.Contract.GameEntry; import ca.josephroque.bowlingcompanion.database.Contract.SeriesEntry; import ca.josephroque.bowlingcompanion.database.DatabaseHelper; import ca.josephroque.bowlingcompanion.fragment.BowlerFragment; import ca.josephroque.bowlingcompanion.fragment.GameFragment; import ca.josephroque.bowlingcompanion.fragment.LeagueEventFragment; import ca.josephroque.bowlingcompanion.fragment.MatchPlayFragment; import ca.josephroque.bowlingcompanion.fragment.SeriesFragment; import ca.josephroque.bowlingcompanion.fragment.StatsGraphFragment; import ca.josephroque.bowlingcompanion.fragment.StatsListFragment; import ca.josephroque.bowlingcompanion.fragment.TransferFragment; import ca.josephroque.bowlingcompanion.theme.Theme; import ca.josephroque.bowlingcompanion.utilities.DateUtils; import ca.josephroque.bowlingcompanion.utilities.DisplayUtils; import ca.josephroque.bowlingcompanion.utilities.EmailUtils; import ca.josephroque.bowlingcompanion.utilities.FloatingActionButtonHandler; import ca.josephroque.bowlingcompanion.utilities.NavigationController; import ca.josephroque.bowlingcompanion.utilities.NavigationUtils; import ca.josephroque.bowlingcompanion.utilities.PermissionUtils; import ca.josephroque.bowlingcompanion.utilities.Startup; import ca.josephroque.bowlingcompanion.view.AnimatedFloatingActionButton; import ca.josephroque.bowlingcompanion.wrapper.Bowler; import ca.josephroque.bowlingcompanion.wrapper.LeagueEvent; import ca.josephroque.bowlingcompanion.wrapper.Series; /** * Created by Joseph Roque. The main activity which handles most interaction with the application. */ @SuppressWarnings("Convert2Lambda") public class MainActivity extends AppCompatActivity implements FragmentManager.OnBackStackChangedListener, Theme.ChangeableTheme, BowlerFragment.BowlerCallback, LeagueEventFragment.LeagueEventCallback, SeriesFragment.SeriesCallback, GameFragment.GameFragmentCallback, NavigationDrawerAdapter.NavigationCallback, ActivityCompat.OnRequestPermissionsResultCallback, NavigationController { /** Identifies output from this class in Logcat. */ @SuppressWarnings("unused") private static final String TAG = "MainActivity"; /** Id of current bowler being used in fragments. */ private long mBowlerId = -1; /** Id of current league being used in fragments. */ private long mLeagueId = -1; /** Id of current series being used in fragments. */ private long mSeriesId = -1; /** Id of current game being used in fragments. */ private long mGameId = -1; /** Number of games in current league/event in fragments. */ private int mDefaultNumberOfGames; /** Number of games in a newly created series. */ private int mNumberOfGamesForSeries; /** Name of current bowler being used in fragments. */ private String mBowlerName; /** Name of current league being used in fragments. */ private String mLeagueName; /** Date of current series being used in fragments. */ private String mSeriesDate; /** Game number in series. */ private int mGameNumber; /** Indicates if the fragments are in event mode or not. */ private boolean mIsEventMode; /** Indicates if a quick series is being created. */ private boolean mIsQuickSeries; /** Indicates the current fragment on screen. */ private String mCurrentFragmentTitle = Constants.FRAGMENT_BOWLERS; /** View which, on click, advances the frame. */ private View mViewAutoAdvance; /** Displays time until auto advance. */ private TextView mTextViewAutoAdvanceStatus; /** Indicates if auto advance has been enabled. */ private boolean mAutoAdvanceEnabled; /** Time to delay auto advance. */ private int mAutoAdvanceDelay; /** Time remaining before auto advance delay expires. */ private int mAutoAdvanceDelayRemaining; /** Indicates if the user should be capable of navigating through the app through interactions with the Activity. */ private boolean mNavigationEnabled = true; /** Handler for posting auto advance. */ private Handler mAutoAdvanceHandler; /** Runnable to auto advance. */ private final Runnable mAutoAdvanceCallback = new Runnable() { @Override public void run() { if (--mAutoAdvanceDelayRemaining <= 0) { mViewAutoAdvance.performClick(); stopAutoAdvanceTimer(); } else { if (mTextViewAutoAdvanceStatus != null) { runOnUiThread(new Runnable() { @Override public void run() { final int timeToDelay = 1000; mTextViewAutoAdvanceStatus.setVisibility(View.VISIBLE); mTextViewAutoAdvanceStatus.setText(String.format( Locale.CANADA, getResources().getString(R.string.text_until_auto_advance_placeholder), mAutoAdvanceDelayRemaining)); mAutoAdvanceHandler.postDelayed(mAutoAdvanceCallback, timeToDelay); } }); } } } }; /** Queue of threads which are waiting to save data to the database. */ private ConcurrentLinkedQueue<Thread> mQueueSavingThreads; /** Current thread saving to the database. */ private Thread mRunningSaveThread; /** Indicates if the app is running and should continue to check for threads trying to save. */ private final AtomicBoolean mAppIsRunning = new AtomicBoolean(false); /** Navigation drawer layout. */ private DrawerLayout mDrawerLayout; /** ListView to display items in navigation drawer. */ private RecyclerView mDrawerRecyclerView; /** Adapter to manage items in navigation drawer. */ private NavigationDrawerAdapter mDrawerAdapter; /** Toggle for the navigation drawer. */ private ActionBarDrawerToggle mDrawerToggle; /** Title of the navigation drawer. */ private int mDrawerTitle; /** Title of the activity for when navigation drawer is closed. */ private int mTitle; /** Items in the navigation drawer. */ private ArrayList<String> mListDrawerOptions; /** AdView to display ads to user. */ private AdView mAdView; /** The primary floating action button. */ private AnimatedFloatingActionButton mPrimaryFab; /** The secondary floating action button. */ private AnimatedFloatingActionButton mSecondaryFab; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Theme.loadTheme(this); if (getResources().getBoolean(R.bool.portrait_only)) setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); setContentView(R.layout.activity_main); mAutoAdvanceHandler = new AutoAdvanceHandler(Looper.getMainLooper()); mTitle = R.string.app_name; mDrawerTitle = R.string.title_drawer; Toolbar toolbar = (Toolbar) findViewById(R.id.main_toolbar); setSupportActionBar(toolbar); setupNavigationDrawer(); setupFloatingActionButton(); mQueueSavingThreads = new ConcurrentLinkedQueue<>(); mRunningSaveThread = null; getSupportFragmentManager().addOnBackStackChangedListener(this); if (getSupportActionBar() != null) getSupportActionBar().setDisplayHomeAsUpEnabled(true); if (savedInstanceState == null) { // Creates new BowlerFragment to display data, if no other fragment exists Fragment bowlerFragment = BowlerFragment.newInstance(); getSupportFragmentManager().beginTransaction() .add(R.id.fl_main_fragment_container, bowlerFragment, Constants.FRAGMENT_BOWLERS) .commit(); PreferenceManager.getDefaultSharedPreferences(MainActivity.this) .edit() .remove(Constants.PREF_FACEBOOK_CLOSED) .apply(); mDefaultNumberOfGames = -1; mNumberOfGamesForSeries = -1; } else { // Loads member variables from bundle mBowlerId = savedInstanceState.getLong(Constants.EXTRA_ID_BOWLER, -1); mLeagueId = savedInstanceState.getLong(Constants.EXTRA_ID_LEAGUE, -1); mSeriesId = savedInstanceState.getLong(Constants.EXTRA_ID_SERIES, -1); mGameId = savedInstanceState.getLong(Constants.EXTRA_ID_GAME, -1); mGameNumber = savedInstanceState.getInt(Constants.EXTRA_GAME_NUMBER, -1); mBowlerName = savedInstanceState.getString(Constants.EXTRA_NAME_BOWLER); mLeagueName = savedInstanceState.getString(Constants.EXTRA_NAME_LEAGUE); mSeriesDate = savedInstanceState.getString(Constants.EXTRA_NAME_SERIES); mDefaultNumberOfGames = savedInstanceState.getInt(Constants.EXTRA_NUMBER_OF_GAMES, -1); mNumberOfGamesForSeries = savedInstanceState.getInt(Constants.EXTRA_GAMES_IN_SERIES, -1); mIsEventMode = savedInstanceState.getBoolean(Constants.EXTRA_EVENT_MODE); mIsQuickSeries = savedInstanceState.getBoolean(Constants.EXTRA_QUICK_SERIES); int navCurrentGameNumber = savedInstanceState.getInt(Constants.EXTRA_NAV_CURRENT_GAME); mDrawerAdapter.setCurrentItem(navCurrentGameNumber); mDrawerAdapter.notifyDataSetChanged(); } setupAdView(); Startup.onStartup(this); } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); mDrawerToggle.syncState(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mDrawerToggle.onConfigurationChanged(newConfig); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // Saves member variables to bundle outState.putLong(Constants.EXTRA_ID_BOWLER, mBowlerId); outState.putLong(Constants.EXTRA_ID_LEAGUE, mLeagueId); outState.putLong(Constants.EXTRA_ID_SERIES, mSeriesId); outState.putLong(Constants.EXTRA_ID_GAME, mGameId); outState.putString(Constants.EXTRA_NAME_BOWLER, mBowlerName); outState.putString(Constants.EXTRA_NAME_LEAGUE, mLeagueName); outState.putString(Constants.EXTRA_NAME_SERIES, mSeriesDate); outState.putInt(Constants.EXTRA_NUMBER_OF_GAMES, mDefaultNumberOfGames); outState.putInt(Constants.EXTRA_GAMES_IN_SERIES, mNumberOfGamesForSeries); outState.putInt(Constants.EXTRA_GAME_NUMBER, mGameNumber); outState.putBoolean(Constants.EXTRA_QUICK_SERIES, mIsQuickSeries); outState.putBoolean(Constants.EXTRA_EVENT_MODE, mIsEventMode); outState.putInt(Constants.EXTRA_NAV_CURRENT_GAME, mDrawerAdapter.getCurrentItem()); } @Override protected void onResume() { super.onResume(); mAppIsRunning.set(true); if (mAdView != null && mAdView.getVisibility() == View.VISIBLE) mAdView.resume(); new Thread(new Runnable() { @Override public void run() { while (mAppIsRunning.get() || mQueueSavingThreads.peek() != null) { mRunningSaveThread = mQueueSavingThreads.peek(); if (mRunningSaveThread != null) { if (mRunningSaveThread.getState() == Thread.State.NEW) { mRunningSaveThread.start(); try { mRunningSaveThread.join(); mQueueSavingThreads.poll(); } catch (InterruptedException ex) { throw new RuntimeException("Error saving game: " + ex.getMessage()); } } else { Log.e(TAG, "Thread already started - " + mRunningSaveThread.getState().toString()); mQueueSavingThreads.poll(); } } else { try { //noinspection CheckStyle Thread.sleep(100); } catch (InterruptedException ex) { throw new RuntimeException("Error while saving thread sleeping: " + ex.getMessage()); } } } } }).start(); updateTheme(); } @Override protected void onPause() { if (mAdView != null && mAdView.getVisibility() == View.VISIBLE) mAdView.pause(); super.onPause(); mAppIsRunning.set(false); } @Override protected void onDestroy() { if (mAdView != null && mAdView.getVisibility() == View.VISIBLE) mAdView.destroy(); super.onDestroy(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.menu_main, menu); return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { // Sets menu items visibility depending on if navigation drawer is open boolean drawerOpen = isDrawerOpen(); menu.findItem(R.id.action_settings).setVisible(!drawerOpen); menu.findItem(R.id.action_tutorial).setVisible(!drawerOpen); return super.onPrepareOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { if (mDrawerLayout.getDrawerLockMode(GravityCompat.START) != DrawerLayout.LOCK_MODE_LOCKED_CLOSED && mDrawerToggle.onOptionsItemSelected(item)) return true; if (!mNavigationEnabled) return true; switch (item.getItemId()) { case android.R.id.home: onBackPressed(); return true; case R.id.action_settings: openSettings(); return true; case R.id.action_tutorial: openSplashActivity(); return true; default: return super.onOptionsItemSelected(item); } } @Override public void onBackStackChanged() { List<Fragment> fragments = getSupportFragmentManager().getFragments(); for (Fragment fragment : fragments) { if (fragment == null || !fragment.isVisible() || fragment.getTag() == null) continue; switch (fragment.getTag()) { case Constants.FRAGMENT_BOWLERS: mBowlerId = -1; mLeagueId = -1; mSeriesId = -1; mGameId = -1; mGameNumber = -1; mBowlerName = null; mLeagueName = null; mSeriesDate = null; mDefaultNumberOfGames = -1; mNumberOfGamesForSeries = -1; mIsQuickSeries = false; mCurrentFragmentTitle = Constants.FRAGMENT_BOWLERS; break; case Constants.FRAGMENT_LEAGUES: mLeagueId = -1; mSeriesId = -1; mGameId = -1; mGameNumber = -1; mLeagueName = null; mSeriesDate = null; mDefaultNumberOfGames = -1; mNumberOfGamesForSeries = -1; mCurrentFragmentTitle = Constants.FRAGMENT_LEAGUES; break; case Constants.FRAGMENT_SERIES: mSeriesId = -1; mGameId = -1; mGameNumber = -1; mSeriesDate = null; mCurrentFragmentTitle = Constants.FRAGMENT_SERIES; break; case Constants.FRAGMENT_GAME: mGameId = -1; mGameNumber = -1; mCurrentFragmentTitle = Constants.FRAGMENT_GAME; learnNavigationDrawer(); break; case Constants.FRAGMENT_STAT_LIST: mCurrentFragmentTitle = Constants.FRAGMENT_STAT_LIST; break; case Constants.FRAGMENT_STAT_GRAPH: mCurrentFragmentTitle = Constants.FRAGMENT_STAT_GRAPH; break; default: return; } mDrawerAdapter.notifyDataSetChanged(); } } @Override public void onBackPressed() { if (!mNavigationEnabled) return; if (isDrawerOpen()) mDrawerLayout.closeDrawer(GravityCompat.START); else { FragmentManager fm = getSupportFragmentManager(); for (Fragment frag : fm.getFragments()) { if (frag != null && frag.isVisible()) { FragmentManager childFm = frag.getChildFragmentManager(); if (childFm.getBackStackEntryCount() > 0) { childFm.popBackStack(); return; } } } super.onBackPressed(); } } @Override public void updateTheme() { // Updates colors and sets theme for MainActivity valid if (getSupportActionBar() != null) getSupportActionBar() .setBackgroundDrawable(new ColorDrawable(Theme.getPrimaryThemeColor())); mDrawerRecyclerView.setBackgroundColor(Theme.getPrimaryThemeColor()); String taskName = getResources().getString(R.string.app_name); try { PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0); // The .debug specified in gradle if (pInfo.packageName.equals("ca.josephroque.bowlingcompanion.debug")) { taskName += " (DEBUG)"; } } catch (PackageManager.NameNotFoundException ex) { Log.e(TAG, "Error finding package name.", ex); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Bitmap icon = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher); setTaskDescription(new ActivityManager.TaskDescription(taskName, icon, Theme.getPrimaryThemeColor())); Window window = getWindow(); // clear FLAG_TRANSLUCENT_STATUS flag: window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); // add FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS flag to the window window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); // finally change the color window.setStatusBarColor(Theme.getStatusThemeColor()); } else { setTitle(taskName); } DisplayUtils.setFloatingActionButtonColors(mPrimaryFab, Theme.getPrimaryThemeColor(), Theme.getTertiaryThemeColor()); DisplayUtils.setFloatingActionButtonColors(mSecondaryFab, Theme.getPrimaryThemeColor(), Theme.getTertiaryThemeColor()); if (isDrawerOpen()) setActionBarTitle(mDrawerTitle, false); else setActionBarTitle(mTitle, false); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == PermissionUtils.REQUEST_EXTERNAL_STORAGE) { if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { GameFragment gameFragment = null; FragmentManager fragmentManager = getSupportFragmentManager(); for (Fragment frag : fragmentManager.getFragments()) { if (frag != null && frag instanceof GameFragment) { gameFragment = (GameFragment) frag; break; } } if (gameFragment != null) gameFragment.externalStoragePermissionGranted(); } } } @Override public void onBowlerSelected(Bowler bowler, boolean openLeagueFragment, boolean isQuickSeries) { mBowlerId = bowler.getBowlerId(); mBowlerName = bowler.getBowlerName(); mIsQuickSeries = isQuickSeries; if (openLeagueFragment) { LeagueEventFragment leagueEventFragment = LeagueEventFragment.newInstance(); startFragmentTransaction(leagueEventFragment, Constants.FRAGMENT_BOWLERS, Constants.FRAGMENT_LEAGUES); } } @Override public void onDataTransferSelected() { startFragmentTransaction(TransferFragment.newInstance(), Constants.FRAGMENT_BOWLERS, Constants.FRAGMENT_TRANSFER); } @Override public void onLeagueSelected(LeagueEvent leagueEvent, boolean openSeriesFragment) { mLeagueId = leagueEvent.getLeagueEventId(); mLeagueName = leagueEvent.getLeagueEventName(); mDefaultNumberOfGames = leagueEvent.getLeagueEventNumberOfGames(); if (openSeriesFragment) { SeriesFragment seriesFragment = SeriesFragment.newInstance(); startFragmentTransaction(seriesFragment, Constants.FRAGMENT_LEAGUES, Constants.FRAGMENT_SERIES); } } @Override public void onSeriesSelected(Series series, boolean isEvent) { mSeriesId = series.getSeriesId(); mSeriesDate = series.getSeriesDate(); if (!isEvent) mNumberOfGamesForSeries = series.getNumberOfGames(); else mNumberOfGamesForSeries = mDefaultNumberOfGames; new OpenSeriesTask(MainActivity.this).execute(isEvent); } @Override public void onCreateNewSeries(boolean isEvent) { boolean promptNumberOfGames = !isEvent && mLeagueName != null && mLeagueName.equals(Constants.NAME_OPEN_LEAGUE); if (promptNumberOfGames) { final NumberPicker numberPicker = new NumberPicker(this); numberPicker.setMaxValue(Constants.MAX_NUMBER_LEAGUE_GAMES); numberPicker.setMinValue(1); numberPicker.setWrapSelectorWheel(false); DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (which == DialogInterface.BUTTON_POSITIVE) { mNumberOfGamesForSeries = (byte) numberPicker.getValue(); new AddSeriesTask(MainActivity.this).execute(); } dialog.dismiss(); } }; new AlertDialog.Builder(this) .setTitle("How many games?") .setView(numberPicker) .setPositiveButton(R.string.dialog_create, listener) .setNegativeButton(R.string.dialog_cancel, listener) .create() .show(); } else { mNumberOfGamesForSeries = mDefaultNumberOfGames; new AddSeriesTask(MainActivity.this).execute(); } } /** * Sets up the navigation drawer for the game fragment. */ public void createGameNavigationDrawer() { mListDrawerOptions.remove(NavigationUtils.NAVIGATION_ITEM_LEAGUES); mListDrawerOptions.remove(NavigationUtils.NAVIGATION_ITEM_SERIES); for (Iterator<String> it = mListDrawerOptions.iterator(); it.hasNext();) if (it.next().matches("\\w+ \\d+")) it.remove(); GameFragment gameFragment = null; FragmentManager fragmentManager = getSupportFragmentManager(); for (Fragment frag : fragmentManager.getFragments()) { if (frag != null && frag instanceof GameFragment) { gameFragment = (GameFragment) frag; break; } } if (gameFragment == null) return; int additionalOffset = 0; if (!isQuickSeries()) mListDrawerOptions.add( NavigationUtils.NAVIGATION_STATIC_ITEMS + additionalOffset++, NavigationUtils.NAVIGATION_ITEM_LEAGUES); if (!isEventMode() && !isQuickSeries()) mListDrawerOptions.add( NavigationUtils.NAVIGATION_STATIC_ITEMS + additionalOffset++, NavigationUtils.NAVIGATION_ITEM_SERIES); final int totalOffset = additionalOffset; for (byte i = 0; i < mNumberOfGamesForSeries; i++) mListDrawerOptions.add( NavigationUtils.NAVIGATION_STATIC_ITEMS + 1 + additionalOffset++, "Game " + (i + 1)); mDrawerAdapter.setCurrentItem(gameFragment.getCurrentGame() + NavigationUtils.NAVIGATION_STATIC_ITEMS + 1 + totalOffset); mDrawerAdapter.setHeaderTitle(mBowlerName); mDrawerAdapter.setHeaderSubtitle(mLeagueName); } /** * Sets the icon of the two floating action buttons with animation, using the theme colors. * * @param primaryDrawableId id of the drawable for the primary floating action button * @param secondaryDrawableId id of the drawable for the secondary floating action button */ public void setFloatingActionButtonState(int primaryDrawableId, int secondaryDrawableId) { setFloatingActionButtonState(primaryDrawableId, Theme.getPrimaryThemeColor(), Theme.getTertiaryThemeColor(), secondaryDrawableId, Theme.getPrimaryThemeColor(), Theme.getTertiaryThemeColor()); } /** * Sets the icon and colors of the two floating action buttons with animation. * * @param primaryDrawableId id of the drawable for the primary floating action button * @param primaryDefaultColor standing color of the primary floating action button * @param primaryPressedColor pressed color of the primary floating action button * @param secondaryDrawableId id of the drawable for the secondary floating action button * @param secondaryDefaultColor standing color of the secondary floating action button * @param secondaryPressedColor pressed color of the secondary floating action button */ public void setFloatingActionButtonState(int primaryDrawableId, int primaryDefaultColor, int primaryPressedColor, int secondaryDrawableId, int secondaryDefaultColor, int secondaryPressedColor) { mPrimaryFab.animate(primaryDrawableId, primaryDefaultColor, primaryPressedColor); mSecondaryFab.animate(secondaryDrawableId, secondaryDefaultColor, secondaryPressedColor); } /** * Begins transaction in FragmentManager to open {@code fragment}. * * @param fragment fragment to open * @param backStackTag tag for current fragment in backstack * @param fragmentTag tag for new fragment in manager */ private void startFragmentTransaction(Fragment fragment, String backStackTag, String fragmentTag) { if (!mNavigationEnabled) return; getSupportFragmentManager().beginTransaction() .setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left, R.anim.slide_in_left, R.anim.slide_out_right) .replace(R.id.fl_main_fragment_container, fragment, fragmentTag) .addToBackStack(backStackTag) .commit(); } @Override public void onBowlerStatsOpened() { openStatsFragment(Constants.FRAGMENT_LEAGUES); } @Override public void onLeagueStatsOpened() { openStatsFragment(Constants.FRAGMENT_SERIES); } @Override public void onSeriesStatsOpened() { openStatsFragment(Constants.FRAGMENT_GAME); } @Override public void onSeriesStatsOpened(Series series) { mSeriesId = series.getSeriesId(); mSeriesDate = series.getSeriesDate(); mNumberOfGamesForSeries = series.getNumberOfGames(); openStatsFragment(Constants.FRAGMENT_GAME); } @Override public void onGameStatsOpened(long gameId, byte gameNumber) { mGameId = gameId; mGameNumber = gameNumber; openStatsFragment(Constants.FRAGMENT_GAME); } @Override public void onGameChanged(final int newGameNumber) { int offset = 0; while (mListDrawerOptions.size() > offset && !mListDrawerOptions.get(offset).matches("\\w+ \\d+")) offset++; int currentAdapterGame = mDrawerAdapter.getCurrentItem() - offset; if (currentAdapterGame == newGameNumber) return; runOnUiThread(new Runnable() { @Override public void run() { mDrawerAdapter.setCurrentItem(newGameNumber); mDrawerAdapter.notifyDataSetChanged(); } }); } @Override public void onNavigationItemClicked(int position) { mDrawerLayout.closeDrawer(GravityCompat.START); if (!mNavigationEnabled) return; if (mListDrawerOptions.get(position).matches("\\w+ \\d+")) { mDrawerLayout.closeDrawer(GravityCompat.START); GameFragment gameFragment = (GameFragment) getSupportFragmentManager() .findFragmentByTag(Constants.FRAGMENT_GAME); if (gameFragment == null || !gameFragment.isVisible()) return; int offset = 0; while (mListDrawerOptions.size() > offset && !mListDrawerOptions.get(offset).matches("\\w+ \\d+")) offset++; gameFragment.switchGame((byte) (position - offset)); return; } switch (mListDrawerOptions.get(position)) { case NavigationUtils.NAVIGATION_ITEM_BOWLERS: getSupportFragmentManager().popBackStack( Constants.FRAGMENT_BOWLERS, FragmentManager.POP_BACK_STACK_INCLUSIVE); break; case NavigationUtils.NAVIGATION_ITEM_LEAGUES: getSupportFragmentManager().popBackStack( Constants.FRAGMENT_LEAGUES, FragmentManager.POP_BACK_STACK_INCLUSIVE); break; case NavigationUtils.NAVIGATION_ITEM_SERIES: getSupportFragmentManager().popBackStack( Constants.FRAGMENT_SERIES, FragmentManager.POP_BACK_STACK_INCLUSIVE); break; case NavigationUtils.NAVIGATION_ITEM_FEEDBACK: Intent emailIntent = EmailUtils.getEmailIntent( "contact@josephroque.ca", String.format(Locale.CANADA, "Comm/Sug: Bowling Companion (%d)", BuildConfig.VERSION_CODE), null); startActivity(Intent.createChooser(emailIntent, "Send mail...")); break; case NavigationUtils.NAVIGATION_ITEM_SETTINGS: openSettings(); break; case NavigationUtils.NAVIGATION_ITEM_HELP: openSplashActivity(); break; default: // do nothing } } @Override public void onUserInteraction() { super.onUserInteraction(); if (mAutoAdvanceEnabled && mCurrentFragmentTitle.equals(Constants.FRAGMENT_GAME)) { resetAutoAdvanceTimer(); } } @Override public void setAutoAdvanceConditions(View clickToAdvance, TextView textViewStatus, boolean enabled, int delay) { mViewAutoAdvance = clickToAdvance; mTextViewAutoAdvanceStatus = textViewStatus; mAutoAdvanceEnabled = enabled; mAutoAdvanceDelay = delay; if (!mAutoAdvanceEnabled) stopAutoAdvanceTimer(); else resetAutoAdvanceTimer(); } @Override public void resetAutoAdvanceTimer() { if (!mAutoAdvanceEnabled) return; if (mTextViewAutoAdvanceStatus != null) mTextViewAutoAdvanceStatus.setVisibility(View.INVISIBLE); final int timeToDelay = 1000; mAutoAdvanceDelayRemaining = mAutoAdvanceDelay; mAutoAdvanceHandler.removeCallbacks(mAutoAdvanceCallback); mAutoAdvanceHandler.postDelayed(mAutoAdvanceCallback, timeToDelay); } @Override public void stopAutoAdvanceTimer() { if (mTextViewAutoAdvanceStatus != null) mTextViewAutoAdvanceStatus.setVisibility(View.INVISIBLE); mAutoAdvanceHandler.removeCallbacks(mAutoAdvanceCallback); } @Override public void updateGameScore(byte gameNumber, short gameScore) { mDrawerAdapter.setSubtitle("Game " + gameNumber, Short.toString(gameScore)); } @Override public void setNavigationEnabled(boolean enable) { mNavigationEnabled = enable; } /** * Sets up the floating action buttons. */ private void setupFloatingActionButton() { // Setting up the appearance and actions of the primary Fab mPrimaryFab = (AnimatedFloatingActionButton) findViewById(R.id.fab_main); DisplayUtils.fixFloatingActionButtonMargins(getResources(), mPrimaryFab); mPrimaryFab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { FragmentManager fragmentManager = getSupportFragmentManager(); for (Fragment fragment : fragmentManager.getFragments()) { if (fragment != null && fragment.isVisible() && fragment instanceof FloatingActionButtonHandler) ((FloatingActionButtonHandler) fragment).onFabClick(); } } }); // Setting up the appearance and actions of the secondary Fab mSecondaryFab = (AnimatedFloatingActionButton) findViewById(R.id.fab_secondary); DisplayUtils.fixFloatingActionButtonMargins(getResources(), mSecondaryFab); mSecondaryFab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { FragmentManager fragmentManager = getSupportFragmentManager(); for (Fragment fragment : fragmentManager.getFragments()) { if (fragment != null && fragment.isVisible() && fragment instanceof FloatingActionButtonHandler) ((FloatingActionButtonHandler) fragment).onSecondaryFabClick(); } } }); } /** * Sets up the navigation drawer. */ @SuppressWarnings("CheckStyle") private void setupNavigationDrawer() { final int displayWidth = getResources().getDisplayMetrics().widthPixels; final int maxNavigationDrawerWidth = (int) Math.ceil( getResources().getDisplayMetrics().density * NavigationUtils.MAX_NAVIGATION_DRAWER_WIDTH_DP); int toolbarHeight = 0; TypedValue typedValue = new TypedValue(); if (getTheme().resolveAttribute(android.R.attr.actionBarSize, typedValue, true)) toolbarHeight = TypedValue.complexToDimensionPixelSize(typedValue.data, getResources().getDisplayMetrics()); mDrawerLayout = (DrawerLayout) findViewById(R.id.main_drawer_layout); mDrawerRecyclerView = (RecyclerView) findViewById(R.id.main_drawer_list); if (mDrawerRecyclerView != null && mDrawerLayout != null) { ViewGroup.LayoutParams layoutParams = mDrawerRecyclerView.getLayoutParams(); layoutParams.width = Math.min(displayWidth - toolbarHeight, maxNavigationDrawerWidth); mDrawerRecyclerView.setLayoutManager(new LinearLayoutManager(this)); mListDrawerOptions = new ArrayList<>(); mListDrawerOptions.add(NavigationUtils.NAVIGATION_ITEM_HEADER); mListDrawerOptions.add(NavigationUtils.NAVIGATION_ITEM_BOWLERS); mListDrawerOptions.add(NavigationUtils.NAVIGATION_SUBHEADER_GAMES); mListDrawerOptions.add(NavigationUtils.NAVIGATION_SUBHEADER_OTHER); mListDrawerOptions.add(NavigationUtils.NAVIGATION_ITEM_FEEDBACK); mListDrawerOptions.add(NavigationUtils.NAVIGATION_ITEM_HELP); mListDrawerOptions.add(NavigationUtils.NAVIGATION_ITEM_SETTINGS); mDrawerAdapter = new NavigationDrawerAdapter(this, mListDrawerOptions); mDrawerAdapter.setPositionToSubheader(NavigationUtils.NAVIGATION_SUBHEADER_GAMES); mDrawerAdapter.setPositionToSubheader(NavigationUtils.NAVIGATION_SUBHEADER_OTHER); mDrawerRecyclerView.setAdapter(mDrawerAdapter); mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, R.string.text_open_drawer, R.string.text_close_drawer) { /** Called when a drawer has settled in a completely closed state. */ @Override public void onDrawerClosed(View view) { super.onDrawerClosed(view); setActionBarTitle(mTitle, false); invalidateOptionsMenu(); } /** Called when a drawer has settled in a completely open state. */ @Override public void onDrawerOpened(View drawerView) { super.onDrawerOpened(drawerView); setActionBarTitle(mDrawerTitle, false); invalidateOptionsMenu(); } @Override public void onDrawerSlide(View drawerView, float slideOffset) { super.onDrawerSlide(drawerView, slideOffset); NavigationUtils.setDrawerOffset(slideOffset); } }; mDrawerLayout.addDrawerListener(mDrawerToggle); } } /** * Sets up the AdView and requests an ad. */ private void setupAdView() { // Sets the adview to display an ad to the user mAdView = (AdView) findViewById(R.id.av_main); mAdView.setAdListener(new AdListener() { @Override public void onAdFailedToLoad(int errorCode) { // If ad fails to load, hides this adview runOnUiThread(new Runnable() { @Override public void run() { mAdView.destroy(); mAdView.setVisibility(View.GONE); } }); } @Override public void onAdLoaded() { runOnUiThread(new Runnable() { @Override public void run() { mAdView.setVisibility(View.VISIBLE); } }); } }); AdRequest.Builder builder = new AdRequest.Builder() .addTestDevice("B3EEABB8EE11C2BE770B684D95219ECB") .addTestDevice("F2B8E706AC77AA09B97D016DB70BF723") .addTestDevice("7387C5A63BE83E951937A7F2842F6C28") .addTestDevice("699FFDF176FEE8F8B6AD7E3D322A43AB"); mAdView.loadAd(builder.build()); } /** * Gets a new instance of {@link ca.josephroque.bowlingcompanion.fragment.StatsListFragment} and displays it. * * @param tag represents fragment which should be returned to when backstack is popped */ private void openStatsFragment(String tag) { StatsListFragment statsListFragment = StatsListFragment.newInstance(); startFragmentTransaction(statsListFragment, tag, Constants.FRAGMENT_STAT_LIST); } /** * Gets a new instance of {@link ca.josephroque.bowlingcompanion.fragment.StatsGraphFragment} and displays it. * * @param statCategory category of stat to display * @param statIndex index in category of stat to display */ public void openStatGraph(int statCategory, int statIndex) { StatsGraphFragment fragment = StatsGraphFragment.newInstance(statCategory, statIndex); startFragmentTransaction(fragment, Constants.FRAGMENT_STAT_LIST, Constants.FRAGMENT_STAT_GRAPH); } /** * Gets a new instance of {@link ca.josephroque.bowlingcompanion.fragment.MatchPlayFragment} and displays it. * * @param gameId id of game o */ public void openMatchPlayStats(long gameId) { MatchPlayFragment fragment = MatchPlayFragment.newInstance(gameId); startFragmentTransaction(fragment, Constants.FRAGMENT_GAME, Constants.FRAGMENT_MATCH_PLAY); } /** * Sets title of action bar to string pointed to by resId. * * @param resId id of string to be set at title * @param override indicates if reference to resId title should be saved in mTitle */ public void setActionBarTitle(int resId, boolean override) { // Changing title theme color // final String hexColor = DataFormatter.getHexColorFromInt(Theme.getHeaderFontThemeColor()); // if (getSupportActionBar() != null) // getSupportActionBar().setTitle(Html.fromHtml("<font color=\"" + hexColor + "\">" // + getResources().getString(resId) + "</font>")); if (getSupportActionBar() != null) getSupportActionBar().setTitle(resId); if (override) mTitle = resId; } /** * Opens the settings activity. */ private void openSettings() { Intent settingsIntent = new Intent(MainActivity.this, ca.josephroque.bowlingcompanion.SettingsActivity.class); startActivity(settingsIntent); } /** * Opens the tutorial. */ private void openSplashActivity() { Intent tutorialIntent = new Intent(MainActivity.this, SplashActivity.class); tutorialIntent.putExtra(Constants.EXTRA_IGNORE_WATCHED, true); startActivity(tutorialIntent); } /** * Returns id of current bowler being used in fragments. * * @return value of mBowlerId */ public long getBowlerId() { return mBowlerId; } /** * Returns id of current league being used in fragments. * * @return value of mLeagueId */ public long getLeagueId() { return mLeagueId; } /** * Returns id of current series being used in fragments. * * @return value of mSeriesId */ public long getSeriesId() { return mSeriesId; } /** * Returns current number of games being used in fragments. * * @return value of mDefaultNumberOfGames */ public int getDefaultNumberOfGames() { return mDefaultNumberOfGames; } /** * Returns current number of games being used for series. * * @return value of mNumberOfGamesForSeries */ public int getNumberOfGamesForSeries() { return mNumberOfGamesForSeries; } /** * Returns name of current bowler being used in fragments. * * @return value of mBowlerName */ public String getBowlerName() { return mBowlerName; } /** * Returns name of current league being used in fragments. * * @return value of mLeagueName */ public String getLeagueName() { return mLeagueName; } /** * Returns id of current game being used in fragments. * * @return value of mGameId */ public long getGameId() { return mGameId; } /** * Returns game number in current series. * * @return value of mGameNumber */ public int getGameNumber() { return mGameNumber; } /** * Returns name of current series being used in fragments. * * @return value of mSeriesId */ public String getSeriesDate() { return mSeriesDate; } /** * Returns true if the navigation drawer is currently open, false otherwise. * * @return true if the drawer is open, false otherwise */ public boolean isDrawerOpen() { return mDrawerLayout.isDrawerOpen(GravityCompat.START); } /** * Returns true if the fragments are in event mode, false otherwise. * * @return the value of mIsEventMode */ public boolean isEventMode() { return mIsEventMode; } /** * Returns true if a quick series is being created, false otherwise. * * @return the value of mIsQuickSeries */ public boolean isQuickSeries() { return mIsQuickSeries; } /** * Loads game data related to seriesId and displays it in a new GameFragment instance. */ private static final class OpenSeriesTask extends AsyncTask<Boolean, Void, Object[]> { /** Weak reference to the parent activity. */ private final WeakReference<MainActivity> mMainActivity; /** * Assigns a weak reference to the parent activity. * * @param activity parent activity */ private OpenSeriesTask(MainActivity activity) { mMainActivity = new WeakReference<>(activity); } @Override protected Object[] doInBackground(Boolean... isEvent) { MainActivity mainActivity = mMainActivity.get(); if (mainActivity == null) return null; long[] gameId = new long[mainActivity.mNumberOfGamesForSeries]; long[] frameId = new long[mainActivity.mNumberOfGamesForSeries * Constants.NUMBER_OF_FRAMES]; boolean[] gameLocked = new boolean[mainActivity.mNumberOfGamesForSeries]; boolean[] manualScore = new boolean[mainActivity.mNumberOfGamesForSeries]; SQLiteDatabase database = DatabaseHelper.getInstance(mainActivity).getReadableDatabase(); String rawSeriesQuery = "SELECT " + "game." + GameEntry._ID + " AS gid, " + GameEntry.COLUMN_IS_LOCKED + ", " + GameEntry.COLUMN_IS_MANUAL + ", " + "frame." + FrameEntry._ID + " AS fid" + " FROM " + GameEntry.TABLE_NAME + " AS game" + " INNER JOIN " + FrameEntry.TABLE_NAME + " AS frame" + " ON gid=" + FrameEntry.COLUMN_GAME_ID + " WHERE " + GameEntry.COLUMN_SERIES_ID + "=?" + " ORDER BY gid, fid"; String[] rawSeriesArgs = {String.valueOf(mainActivity.mSeriesId)}; int currentGame = -1; long currentGameId = -1; int currentFrame = -1; Cursor cursor = database.rawQuery(rawSeriesQuery, rawSeriesArgs); if (cursor.moveToFirst()) { while (!cursor.isAfterLast()) { long newGameId = cursor.getLong(cursor.getColumnIndex("gid")); if (newGameId == currentGameId) frameId[++currentFrame] = cursor.getLong(cursor.getColumnIndex("fid")); else { currentGameId = newGameId; frameId[++currentFrame] = cursor.getLong(cursor.getColumnIndex("fid")); gameId[++currentGame] = currentGameId; gameLocked[currentGame] = cursor.getInt( cursor.getColumnIndex(GameEntry.COLUMN_IS_LOCKED)) == 1; manualScore[currentGame] = cursor.getInt( cursor.getColumnIndex(GameEntry.COLUMN_IS_MANUAL)) == 1; } cursor.moveToNext(); } } cursor.close(); return new Object[]{gameId, frameId, gameLocked, manualScore, isEvent[0]}; } @SuppressWarnings("CheckStyle") @Override protected void onPostExecute(Object[] params) { MainActivity mainActivity = mMainActivity.get(); if (mainActivity == null || params == null) return; long[] gameIds = (long[]) params[0]; long[] frameIds = (long[]) params[1]; boolean[] gameLocked = (boolean[]) params[2]; boolean[] manualScore = (boolean[]) params[3]; mainActivity.mIsEventMode = (boolean) params[4]; GameFragment gameFragment = GameFragment.newInstance(gameIds, frameIds, gameLocked, manualScore); mainActivity.startFragmentTransaction(gameFragment, (mainActivity.isEventMode() ? Constants.FRAGMENT_LEAGUES : Constants.FRAGMENT_SERIES), Constants.FRAGMENT_GAME); } } /** * Creates a new series in the database and displays it in a new instance of GameFragment. */ private static final class AddSeriesTask extends AsyncTask<Void, Void, Object[]> { /** Weak reference to the parent activity. */ private final WeakReference<MainActivity> mMainActivity; /** * Assigns a weak reference to the parent activity. * * @param activity parent activity */ private AddSeriesTask(MainActivity activity) { mMainActivity = new WeakReference<>(activity); } @Override protected Object[] doInBackground(Void... params) { MainActivity mainActivity = mMainActivity.get(); if (mainActivity == null) return null; long seriesId = -1; long[] gameId = new long[mainActivity.mNumberOfGamesForSeries]; long[] frameId = new long[mainActivity.mNumberOfGamesForSeries * Constants.NUMBER_OF_FRAMES]; SQLiteDatabase database = DatabaseHelper.getInstance(mainActivity).getReadableDatabase(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CANADA); String seriesDate = dateFormat.format(new Date()); database.beginTransaction(); try { ContentValues values = new ContentValues(); values.put(SeriesEntry.COLUMN_SERIES_DATE, seriesDate); values.put(SeriesEntry.COLUMN_LEAGUE_ID, mainActivity.mLeagueId); seriesId = database.insert(SeriesEntry.TABLE_NAME, null, values); for (byte i = 0; i < mainActivity.mNumberOfGamesForSeries; i++) { values = new ContentValues(); values.put(GameEntry.COLUMN_GAME_NUMBER, i + 1); values.put(GameEntry.COLUMN_SCORE, (short) 0); values.put(GameEntry.COLUMN_SERIES_ID, seriesId); gameId[i] = database.insert(GameEntry.TABLE_NAME, null, values); for (byte j = 0; j < Constants.NUMBER_OF_FRAMES; j++) { values = new ContentValues(); values.put(FrameEntry.COLUMN_FRAME_NUMBER, j + 1); values.put(FrameEntry.COLUMN_GAME_ID, gameId[i]); frameId[j + Constants.NUMBER_OF_FRAMES * i] = database.insert(FrameEntry.TABLE_NAME, null, values); } } database.setTransactionSuccessful(); } catch (Exception ex) { throw new RuntimeException("Could not create new series entry in database: " + ex.getMessage()); } finally { database.endTransaction(); } mainActivity.mSeriesId = seriesId; mainActivity.mSeriesDate = DateUtils.formattedDateToPrettyCompact(seriesDate); return new Object[]{gameId, frameId}; } @Override protected void onPostExecute(Object[] params) { MainActivity mainActivity = mMainActivity.get(); if (mainActivity == null || params == null) return; long[] gameIds = (long[]) params[0]; long[] frameIds = (long[]) params[1]; mainActivity.mIsEventMode = false; GameFragment gameFragment = GameFragment.newInstance( gameIds, frameIds, new boolean[mainActivity.mNumberOfGamesForSeries], new boolean[mainActivity.mNumberOfGamesForSeries]); mainActivity.startFragmentTransaction( gameFragment, (mainActivity.isQuickSeries() ? Constants.FRAGMENT_BOWLERS : Constants.FRAGMENT_SERIES), Constants.FRAGMENT_GAME); } } /** * Queues a new thread to save data to database. * * @param thread saving thread */ public void addSavingThread(Thread thread) { mQueueSavingThreads.add(thread); } /** * Waits thread until all saving threads in the queue have finished. * * @param activity source activity */ public static void waitForSaveThreads(WeakReference<MainActivity> activity) { // Waits for saving to database to finish, before loading from database while (activity.get() != null && activity.get().mQueueSavingThreads.peek() != null) { try { //noinspection CheckStyle Thread.sleep(100); } catch (InterruptedException ex) { throw new RuntimeException("Could not wait for threads to finish saving: " + ex.getMessage()); } // wait for saving threads to finish } } /** * Enables or disables the navigation drawer and its menu icon. * * @param isEnabled true to enable the drawer, false to disable */ public void setDrawerState(boolean isEnabled) { if (isEnabled) { mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED); mDrawerToggle.setDrawerIndicatorEnabled(true); mDrawerToggle.syncState(); } else { mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); mDrawerToggle.setDrawerIndicatorEnabled(false); mDrawerToggle.syncState(); } } /** * Checks if the user has opened the navigation drawer and, if not, opens it. */ private void learnNavigationDrawer() { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); if (!preferences.getBoolean(NavigationUtils.NAVIGATION_DRAWER_LEARNED, false)) { mDrawerLayout.openDrawer(GravityCompat.START); preferences.edit().putBoolean(NavigationUtils.NAVIGATION_DRAWER_LEARNED, true).apply(); } } @Override public void loadGameScoresForDrawer(final long[] gameIds) { new Thread(new Runnable() { @Override public void run() { final short[] gameScores = new short[gameIds.length]; SQLiteDatabase database = DatabaseHelper.getInstance(MainActivity.this) .getReadableDatabase(); String rawScoreQuery = "SELECT " + GameEntry.COLUMN_SCORE + " FROM " + GameEntry.TABLE_NAME + " WHERE " + GameEntry._ID + " in " + Arrays.toString(gameIds).replace("[", "(").replace("]", ")") + " ORDER BY " + GameEntry._ID; Cursor cursor = database.rawQuery(rawScoreQuery, null); int curGame = 0; if (cursor.moveToFirst()) { while (!cursor.isAfterLast()) { gameScores[curGame++] = (short) cursor.getInt(cursor.getColumnIndex( GameEntry.COLUMN_SCORE)); cursor.moveToNext(); } } cursor.close(); runOnUiThread(new Runnable() { @Override public void run() { for (int i = 0; i < gameIds.length; i++) { mDrawerAdapter.setSubtitle("Game " + (i + 1), Short.toString(gameScores[i])); } } }); } }).start(); } /** * To delay auto advancing. */ private static final class AutoAdvanceHandler extends Handler { /** * Sends {@code Looper} to super class. * * @param looper looper */ private AutoAdvanceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message message) { // does nothing; } } }