/* * @copyright 2012 Philip Warner * @license GNU General Public License * * This file is part of Book Catalogue. * * Book Catalogue is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Book Catalogue is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Book Catalogue. If not, see <http://www.gnu.org/licenses/>. */ package com.eleybourn.bookcatalogue; import java.lang.ref.WeakReference; import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnDismissListener; import android.content.Intent; import android.content.SharedPreferences.Editor; import android.os.Bundle; import android.os.Handler; import android.text.Html; import com.eleybourn.bookcatalogue.booklist.BooklistPreferencesActivity; import com.eleybourn.bookcatalogue.utils.Logger; import com.eleybourn.bookcatalogue.utils.SimpleTaskQueue; import com.eleybourn.bookcatalogue.utils.SimpleTaskQueue.OnTaskFinishListener; import com.eleybourn.bookcatalogue.utils.SimpleTaskQueue.SimpleTask; import com.eleybourn.bookcatalogue.utils.SimpleTaskQueue.SimpleTaskContext; import com.eleybourn.bookcatalogue.utils.UpgradeMessageManager; import com.eleybourn.bookcatalogue.utils.Utils; /** * Single Activity to be the 'Main' activity for the app. I does app-startup stuff which is initially * to start the 'real' main activity. * * Note that calling the desired main activity first resulted in MainMenu's 'singleInstance' property * NOT being honoured. So we call MainMenu anyway, but set a flag in the Intent to indicate this is * a startup. This approach mostly works, but results in an apparent misordering of the activity * stack, which we can live with for now. * * @author Philip Warner */ public class StartupActivity extends Activity { private static String TAG = "StartupActivity"; /** Flag to indicate FTS rebuild is required at startup */ private static String PREF_FTS_REBUILD_REQUIRED = TAG + ".FtsRebuildRequired"; private static String PREF_AUTHOR_SERIES_FIXUP_REQUIRED = TAG + ".FAuthorSeriesFixupRequired"; private static final String STATE_OPENED = "state_opened"; /** Number of times the app has been started */ private static final String PREF_START_COUNT = "Startup.StartCount"; /** Number of app startups between offers to backup */ private static final int BACKUP_PROMPT_WAIT = 5; /** Number of app startups between displaying the Amazon hint */ private static final int AMAZON_PROMPT_WAIT = 7; /** Indicates the upgrade message has been shown */ private static boolean mUpgradeMessageShown = false; /** Flag set to true on first call */ private static boolean mIsReallyStartup = true; /** Flag indicating a StartupActivity has been created in this session */ private static boolean mHasBeenCalled = false; /** Queue for executing startup tasks, if any */ private SimpleTaskQueue mTaskQueue = null; /** Progress Dialog for startup tasks */ private ProgressDialog mProgress = null; /** Flag indicating THIS instance was really the startup instance */ private boolean mWasReallyStartup = false; /** Flag indicating an export is required after startup */ private boolean mExportRequired = false; /** Flag indicating Amazon hint could be shown */ private static boolean mShowAmazonHint = false; /** Database connection */ //CatalogueDBAdapter mDb = null; /** Handler to post runnables to UI thread */ private Handler mHandler = new Handler(); /**UI thread */ private Thread mUiThread; /** Set the flag to indicate an FTS rebuild is required */ public static void scheduleFtsRebuild() { BookCatalogueApp.getAppPreferences().setBoolean(PREF_FTS_REBUILD_REQUIRED, true); } /** Set the flag to indicate an FTS rebuild is required */ public static void scheduleAuthorSeriesFixup() { BookCatalogueApp.getAppPreferences().setBoolean(PREF_AUTHOR_SERIES_FIXUP_REQUIRED, true); } public static WeakReference<StartupActivity> mStartupActivity = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); System.out.println("Startup isTaskRoot() = " + isTaskRoot()); mHasBeenCalled = true; mUiThread = Thread.currentThread(); // Create a progress dialog; we may not use it...but we need it to be created in the UI thread. mProgress = ProgressDialog.show(this, getString(R.string.book_catalogue_startup), getString(R.string.starting_up), true, true, new OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { // Cancelling the list cancels the activity. StartupActivity.this.finish(); }}); mWasReallyStartup = mIsReallyStartup; // If it's a real application startup...cleanup old stuff if (mWasReallyStartup) { mStartupActivity = new WeakReference<StartupActivity>(this); updateProgress("Starting"); SimpleTaskQueue q = getQueue(); // Always enqueue it; it will get a DB and check if required... q.enqueue(new RebuildFtsTask()); q.enqueue(new AnalyzeDbTask()); // Remove old logs Logger.clearLog(); // Clear the flag mIsReallyStartup = false; // ENHANCE: add checks for new Events/crashes } // If no tasks were queued, then move on to stage 2. Otherwise, the completed // tasks will cause stage 2 to start. if (mTaskQueue == null) stage2Startup(); } /** * Kludge to get a reference to the currently running StartupActivity, if defined. * * @return Reference or null. */ public static StartupActivity getActiveActivity() { if (mStartupActivity != null) return mStartupActivity.get(); else return null; } /** * Update the progress dialog, if it has not been dismissed. * * @param message */ public void updateProgress(final int stringId) { updateProgress(getString(stringId)); } /** * Update the progress dialog, if it has not been dismissed. * * @param message */ public void updateProgress(final String message) { // If mProgress is null, it has been dismissed. Don't update. if (mProgress == null) { return; } // If we are in the UI thread, update the progress. if (Thread.currentThread().equals(mUiThread)) { // There is a small chance that this message could be set to display *after* the activity is finished, // so we check and we also trap, log and ignore errors. // See http://code.google.com/p/android/issues/detail?id=3953 if (!isFinishing()) { try { mProgress.setMessage(message); if (!mProgress.isShowing()) mProgress.show(); } catch (Exception e) { Logger.logError(e); } } } else { // If we are NOT in the UI thread, queue it to the UI thread. mHandler.post(new Runnable() { @Override public void run() { updateProgress(message); }}); } } /** * Called in the UI thread when any startup task completes. If no more tasks, start stage 2. * Because it is in the UI thread, it is not possible for this code to be called until after * onCreate() completes, so a race condition is not possible. Equally well, tasks should only * be queued in onCreate(). */ private void taskCompleted(SimpleTask task) { System.out.println("Task Completed: " + task.getClass().getSimpleName()); if (!mTaskQueue.hasActiveTasks()) { System.out.println("Task Completed - no more"); stage2Startup(); } } /** * Get (or create) the task queue. * * @return */ private SimpleTaskQueue getQueue() { if (mTaskQueue == null) { mTaskQueue = new SimpleTaskQueue("startup-tasks", 1); // Listen for task completions mTaskQueue.setTaskFinishListener(new OnTaskFinishListener() { @Override public void onTaskFinish(SimpleTask task, Exception e) { taskCompleted(task); }}); } return mTaskQueue; } /** * Called in UI thread after last startup task completes, or if there are no tasks to queue. */ private void stage2Startup() { // Remove the weak reference. Only used by db onUpgrade. mStartupActivity.clear(); // Get rid of the progress dialog if (mProgress != null) { mProgress.dismiss(); mProgress = null; } // Display upgrade message if necessary, otherwise go on to stage 3 if (mUpgradeMessageShown || UpgradeMessageManager.getUpgradeMessage().equals("")) { stage3Startup(); } else { upgradePopup(UpgradeMessageManager.getUpgradeMessage()); } } /** * Start the main book list */ private void doMyBooks() { Intent i = new Intent(this, BooksOnBookshelf.class); if (mWasReallyStartup) i.putExtra("startup", true); // XXX: This is nasty, now we use fragments, StartupActivity shoud be a FragmenActivity and load the right fragment // then we could do away with the whole isRoot/willBeRoot thing i.putExtra("willBeTaskRoot", isTaskRoot()); startActivity(i); } /** * Start the main menu */ private void doMainMenu() { Intent i = new Intent(this, MainMenu.class); if (mWasReallyStartup) i.putExtra("startup", true); i.putExtra("willBeTaskRoot", isTaskRoot()); startActivity(i); } public void stage3Startup() { BookCataloguePreferences prefs = BookCatalogueApp.getAppPreferences(); int opened = prefs.getInt(STATE_OPENED, BACKUP_PROMPT_WAIT); int startCount = prefs.getInt(PREF_START_COUNT, 0) + 1; Editor ed = prefs.edit(); if (opened == 0) { ed.putInt(STATE_OPENED, BACKUP_PROMPT_WAIT); } else { ed.putInt(STATE_OPENED, opened - 1); } ed.putInt(PREF_START_COUNT, startCount); ed.commit(); if ( ( startCount % AMAZON_PROMPT_WAIT) == 0) { mShowAmazonHint = true; } mExportRequired = false; if (opened == 0) { AlertDialog alertDialog = new AlertDialog.Builder(this).setMessage(R.string.backup_request).create(); alertDialog.setTitle(R.string.backup_title); alertDialog.setIcon(android.R.drawable.ic_menu_info_details); alertDialog.setButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); alertDialog.setButton2("OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { mExportRequired = true; dialog.dismiss(); } }); alertDialog.setOnDismissListener(new OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { stage4Startup(); }}); alertDialog.show(); } else { stage4Startup(); } } /** * Start whatever activity the user expects */ private void stage4Startup() { BookCataloguePreferences prefs = BookCatalogueApp.getAppPreferences(); // Handle startup specially. // Check if we really want to start this activity. if (prefs.getStartInMyBook()) { doMyBooks(); } else { doMainMenu(); } if (mExportRequired) { AdministrationFunctions.backupCatalogue(this); // Intent i = new Intent(this, AdministrationFunctions.class); // i.putExtra(AdministrationFunctions.DOAUTO, "export"); // startActivity(i); } // We are done finish(); } /** * This will display a popup with a provided message to the user. This will be * mostly used for upgrade notifications * * @param message The message to display in the popup */ public void upgradePopup(String message) { AlertDialog alertDialog = new AlertDialog.Builder(this).setMessage(Html.fromHtml(message)).create(); alertDialog.setTitle(R.string.upgrade_title); alertDialog.setIcon(android.R.drawable.ic_menu_info_details); alertDialog.setButton(getString(R.string.ok), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { UpgradeMessageManager.setMessageAcknowledged(); stage3Startup(); } }); alertDialog.show(); mUpgradeMessageShown = true; } @Override public void onDestroy() { super.onDestroy(); if (mTaskQueue != null) mTaskQueue.finish(); } /** * Task to rebuild FTS in background. Can take several seconds, so not done in onUpgrade(). * * @author Philip Warner * */ public class RebuildFtsTask implements SimpleTask { @Override public void run(SimpleTaskContext taskContext) { // Get a DB to make sure the FTS rebuild flag is set appropriately CatalogueDBAdapter db = taskContext.getDb(); BookCataloguePreferences prefs = BookCatalogueApp.getAppPreferences(); if (prefs.getBoolean(PREF_FTS_REBUILD_REQUIRED, false)) { updateProgress(getString(R.string.rebuilding_search_index)); db.rebuildFts(); prefs.setBoolean(PREF_FTS_REBUILD_REQUIRED, false); } } @Override public void onFinish(Exception e) {} } public class AnalyzeDbTask implements SimpleTask { @Override public void run(SimpleTaskContext taskContext) { CatalogueDBAdapter db = taskContext.getDb(); BookCataloguePreferences prefs = BookCatalogueApp.getAppPreferences(); updateProgress(getString(R.string.optimizing_databases)); // Analyze DB db.analyzeDb(); if (BooklistPreferencesActivity.isThumbnailCacheEnabled()) { // Analyze the covers DB Utils utils = taskContext.getUtils(); utils.analyzeCovers(); } if (prefs.getBoolean(PREF_AUTHOR_SERIES_FIXUP_REQUIRED, false)) { db.fixupAuthorsAndSeries(); prefs.setBoolean(PREF_AUTHOR_SERIES_FIXUP_REQUIRED, false); } } @Override public void onFinish(Exception e) {} } public static boolean hasBeenCalled() { return mHasBeenCalled; } public static boolean getShowAmazonHint() { return mShowAmazonHint; } }