com.radicaldynamic.groupinform.activities.LauncherActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.radicaldynamic.groupinform.activities.LauncherActivity.java

Source

/*
 * Copyright (C) 2009 University of Washington
 * 
 * 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 com.radicaldynamic.groupinform.activities;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;

import org.odk.collect.android.utilities.FileUtils;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.Gravity;
import android.view.Window;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ImageView.ScaleType;

import com.couchbase.libcouch.CouchDB;
import com.couchbase.libcouch.CouchInstaller;
import com.couchbase.libcouch.CouchService;
import com.couchbase.libcouch.ICouchClient;
import com.radicaldynamic.groupinform.R;
import com.radicaldynamic.groupinform.application.Collect;
import com.radicaldynamic.groupinform.logic.InformOnlineState;
import com.radicaldynamic.groupinform.services.DatabaseService;
import com.radicaldynamic.groupinform.services.InformOnlineService;
import com.radicaldynamic.groupinform.utilities.FileUtilsExtended;

/**
 * Application initialization: registration, login, database installation & init 
 */
public class LauncherActivity extends Activity {
    private static final String t = "LauncherActivity: ";

    // Dialog constants
    private static final int DIALOG_COUCH_ERROR = 1;
    private static final int DIALOG_EXTERNAL_STORAGE_UNAVAILABLE = 2;
    private static final int DIALOG_EXPIRED = 3;
    private static final int DIALOG_EXPIRED_CANNOT_CONNECT = 4;
    private static final int DIALOG_UNABLE_TO_CONNECT_OFFLINE_DISABLED = 5;
    private static final int DIALOG_UNABLE_TO_CONNECT_OFFLINE_ENABLED = 6;
    private static final int DIALOG_UNABLE_TO_REGISTER = 7;
    private static final int DIALOG_UPGRADE_FAILED = 8;

    // Intent status codes
    private static final String KEY_REINIT_IOSERVICE = "key_reinit_ioservice";

    private static final int BROWSER_ACTIVITY = 1;

    private ProgressDialog mProgressDialog;
    private Toast mSplashToast;
    private TextView mProgressLoading;

    private boolean mExitApplication = false;

    private final ICouchClient mCouchCallback = new ICouchClient.Stub() {
        @Override
        public void couchStarted(String host, int port) {
            if (mProgressDialog != null) {
                mProgressDialog.dismiss();
            }

            // Persistent service
            startService(new Intent(LauncherActivity.this, CouchService.class));

            Collect.getInstance().getDbService().setLocalDatabaseInfo(host, port);
            startActivityForResult(new Intent(LauncherActivity.this, BrowserActivity.class), BROWSER_ACTIVITY);
        }

        @Override
        public void installing(int completed, int total) {
            if (mProgressDialog == null) {
                mProgressDialog = new ProgressDialog(LauncherActivity.this);
                mProgressDialog.setTitle(" ");
                mProgressDialog.setCancelable(false);
                mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                mProgressDialog.show();
            }

            mProgressDialog.setTitle("Initializing Database");
            mProgressDialog.setProgress(completed);
            mProgressDialog.setMax(total);
        }

        @Override
        public void exit(String error) {
            if (Collect.Log.ERROR)
                Log.e(Collect.LOGTAG, "CouchDB error: " + error);
            showDialog(DIALOG_COUCH_ERROR);
        }
    };

    // Service handling for our connection to databases provided by Couch
    private ServiceConnection mDatabaseConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            if (Collect.Log.DEBUG)
                Log.d(Collect.LOGTAG, t + "mDatabaseConnection: onServiceConnected()");
            Collect.getInstance().setDbService(((DatabaseService.LocalBinder) service).getService());
        }

        public void onServiceDisconnected(ComponentName className) {
            if (Collect.Log.DEBUG)
                Log.d(Collect.LOGTAG, t + "mDatabaseConnection: onServiceDisconnected()");
            Collect.getInstance().setDbService(null);
        }
    };

    // Service handling for our connection to Inform Online
    private ServiceConnection mOnlineConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            if (Collect.Log.DEBUG)
                Log.d(Collect.LOGTAG, t + "mOnlineConnection: onServiceConnected()");
            Collect.getInstance().setIoService(((InformOnlineService.LocalBinder) service).getService());
        }

        public void onServiceDisconnected(ComponentName className) {
            if (Collect.Log.DEBUG)
                Log.d(Collect.LOGTAG, t + "mOnlineConnection: onServiceDisconnected()");
            Collect.getInstance().setIoService(null);
        }
    };

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);

        if (resultCode == RESULT_CANCELED)
            return;

        switch (requestCode) {
        case BROWSER_ACTIVITY:
            if (intent instanceof Intent && intent.hasExtra("exit_app"))
                mExitApplication = true;

            finish();
            break;
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // If SD card error, quit
        if (!FileUtilsExtended.storageReady())
            showDialog(DIALOG_EXTERNAL_STORAGE_UNAVAILABLE);

        Intent intent = getIntent();

        if (intent == null) {
        } else {
            if (intent.getBooleanExtra(KEY_REINIT_IOSERVICE, false)) {
                if (Collect.getInstance().getIoService() instanceof InformOnlineService)
                    Collect.getInstance().getIoService().reinitializeService();
            }
        }

        displaySplash();

        requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
        setContentView(R.layout.launcher);

        mProgressLoading = (TextView) findViewById(R.id.progressLoading);

        startService(new Intent(this, InformOnlineService.class));
        startService(new Intent(this, DatabaseService.class));

        new InitializeApplicationTask().execute(getApplicationContext());
    }

    public Dialog onCreateDialog(int id) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        Dialog dialog = null;

        if (isFinishing()) {
            return dialog;
        }

        switch (id) {
        case DIALOG_COUCH_ERROR:
            builder.setMessage("Error Initializing or Starting Database")
                    .setPositiveButton("Try Again?", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int id) {
                            startCouch();
                        }
                    }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int id) {
                            LauncherActivity.this.moveTaskToBack(true);
                        }
                    });

            dialog = builder.create();
            break;

        case DIALOG_EXPIRED:
            builder.setCancelable(false).setIcon(R.drawable.ic_dialog_alert).setTitle("Subscription Expired")
                    .setMessage(
                            "The subscription for this device profile has expired.\n\nPlease see your account owner to have the subscription renewed or this profile transferred to an active subscription or email our support team at\n\nsupport@groupcomplete.com");

            builder.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                    exitApplication();
                }
            });

            dialog = builder.create();
            break;

        case DIALOG_EXPIRED_CANNOT_CONNECT:
            builder.setCancelable(false).setIcon(R.drawable.ic_dialog_alert).setTitle("Subscription Expired")
                    .setMessage(
                            "The subscription for this device profile has expired.\n\nPlease see your account owner to have the subscription renewed or this profile transferred to an active subscription or email our support team at\n\nsupport@groupcomplete.com\n\nPlease note that the Group Complete service could not be contacted so this message may not reflect up-to-date changes to your Group Complete account.  Please ensure that this device can access the Internet and restart GC Mobile.");

            builder.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                    exitApplication();
                }
            });

            dialog = builder.create();
            break;

        case DIALOG_EXTERNAL_STORAGE_UNAVAILABLE:
            builder.setCancelable(false).setIcon(R.drawable.ic_dialog_alert)
                    .setMessage(getString(R.string.no_sd_error));

            builder.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                    finish();
                }
            });

            dialog = builder.create();
            break;

        // Registered    
        case DIALOG_UNABLE_TO_CONNECT_OFFLINE_DISABLED:
            String msg;

            if (Collect.getInstance().getInformOnlineState().hasReplicatedFolders())
                msg = getString(R.string.tf_connection_error_registered_with_db_msg);
            else
                msg = getString(R.string.tf_connection_error_registered_without_db_msg);

            builder.setCancelable(false).setIcon(R.drawable.ic_dialog_alert).setTitle(R.string.tf_unable_to_connect)
                    .setMessage(msg);

            builder.setPositiveButton(getText(R.string.tf_retry), new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                    restartActivity(true);
                }
            });

            if (Collect.getInstance().getInformOnlineState().hasReplicatedFolders()) {
                builder.setNeutralButton(getText(R.string.tf_go_offline), new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int whichButton) {
                        startCouch();
                    }
                });
            }

            builder.setNegativeButton(getText(R.string.tf_exit_inform), new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                    finish();
                }
            });

            dialog = builder.create();
            break;

        // Registered
        case DIALOG_UNABLE_TO_CONNECT_OFFLINE_ENABLED:
            builder.setCancelable(false).setIcon(R.drawable.ic_dialog_info)
                    .setTitle(R.string.tf_offline_mode_enabled)
                    .setMessage(getString(R.string.tf_offline_mode_enabled_msg));

            builder.setPositiveButton(getText(R.string.tf_continue), new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                    startCouch();
                }
            });

            dialog = builder.create();
            break;

        case DIALOG_UNABLE_TO_REGISTER:
            builder.setCancelable(false).setIcon(R.drawable.ic_dialog_alert).setTitle(R.string.tf_unable_to_connect)
                    .setMessage(getString(R.string.tf_connection_error_unregistered_msg));

            builder.setPositiveButton(getText(R.string.tf_retry), new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                    restartActivity(true);
                }
            });

            builder.setNegativeButton(getText(R.string.tf_exit_inform), new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                    finish();
                }
            });

            dialog = builder.create();
            break;

        case DIALOG_UPGRADE_FAILED:
            builder.setMessage("Error Upgrading Application")
                    .setPositiveButton("Try Again?", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int id) {
                            restartActivity(true);
                        }
                    }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int id) {
                            LauncherActivity.this.moveTaskToBack(true);
                        }
                    });

            dialog = builder.create();
            break;
        }

        return dialog;
    }

    /*
     * (non-Javadoc)
     * @see android.app.ListActivity#onDestroy()
     * 
     * Recall:
     * Because onPause() is the first of the three [killable methods], it's the only one that's guaranteed to be called 
     * before the process is killed  onStop() and onDestroy() may not be. Therefore, you should use onPause() to write 
     * any persistent data (such as user edits) to storage. 
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();

        final String tt = t + "onDestroy(): ";

        // Unbind from our services
        if (Collect.getInstance().getCouchService() instanceof ServiceConnection) {
            try {
                if (Collect.Log.DEBUG)
                    Log.d(Collect.LOGTAG, tt + "unbinding from CouchService");
                unbindService(Collect.getInstance().getCouchService());
            } catch (IllegalArgumentException e) {
                if (Collect.Log.WARN)
                    Log.w(Collect.LOGTAG, tt + "CouchService not registered: " + e.toString());
            }
        }

        if (Collect.getInstance().getDbService() instanceof DatabaseService) {
            try {
                if (Collect.Log.DEBUG)
                    Log.d(Collect.LOGTAG, tt + "unbinding from DatabaseService");
                unbindService(mDatabaseConnection);
            } catch (IllegalArgumentException e) {
                if (Collect.Log.WARN)
                    Log.w(Collect.LOGTAG, tt + "DatabaseService not registered: " + e.toString());
            }
        }

        if (Collect.getInstance().getIoService() instanceof InformOnlineService) {
            try {
                if (Collect.Log.DEBUG)
                    Log.d(Collect.LOGTAG, tt + "unbinding from InformOnlineService");
                unbindService(mOnlineConnection);
            } catch (IllegalArgumentException e) {
                if (Collect.Log.WARN)
                    Log.w(Collect.LOGTAG, tt + "InformOnlineService not registered: " + e.toString());
            }
        }

        // User has reset GC Mobile (see http://stackoverflow.com/questions/2042222/close-application)
        if (mExitApplication) {
            exitApplication();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();

        final String tt = t + "onResume(): ";

        if (Collect.getInstance().getIoService() == null) {
            if (bindService(new Intent(LauncherActivity.this, InformOnlineService.class), mOnlineConnection,
                    Context.BIND_AUTO_CREATE)) {
                if (Collect.Log.DEBUG)
                    Log.d(Collect.LOGTAG, tt + "bound to InformOnlineService");
            }
        }

        if (Collect.getInstance().getDbService() == null) {
            if (bindService(new Intent(LauncherActivity.this, DatabaseService.class), mDatabaseConnection,
                    Context.BIND_AUTO_CREATE)) {
                if (Collect.Log.DEBUG)
                    Log.d(Collect.LOGTAG, tt + "bound to DatabaseService");
            }
        }
    }

    public class InitializeApplicationTask extends AsyncTask<Object, Void, Void> {
        private boolean pinged = false;
        private boolean registered = false;
        private boolean upgradeFailed = false;

        @Override
        protected Void doInBackground(Object... args) {
            // Timer
            int seconds = 0;

            // Prepare environment for new Couch packaging if need be
            try {
                couchPackageUpgradePath();
            } catch (IOException e) {
                if (Collect.Log.ERROR)
                    Log.e(Collect.LOGTAG, t + "upgrade path failed at some point " + e.toString());
                e.printStackTrace();
                upgradeFailed = true;
            }

            // Go through and remove any outdated files & directories
            FileUtilsExtended.expireExternalCache(new File(FileUtilsExtended.EXTERNAL_CACHE));

            // Create directories
            FileUtils.createFolder(FileUtilsExtended.EXTERNAL_CACHE);
            FileUtils.createFolder(FileUtilsExtended.EXTERNAL_DB);
            FileUtils.createFolder(FileUtilsExtended.EXTERNAL_FILES);

            // The InformOnlineService will perform ping and check-in immediately (no need to duplicate here)
            while (true) {
                // Either break out if we were successful in connecting or we have waited too long
                if ((Collect.getInstance().getIoService() instanceof InformOnlineService
                        && Collect.getInstance().getIoService().isInitialized()) || seconds > 30)
                    break;

                if (Collect.getInstance().getIoService() instanceof InformOnlineService && (seconds % 6 == 0))
                    Collect.getInstance().getIoService().goOnline();

                try {
                    Thread.sleep(1000);
                    seconds++;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            pinged = Collect.getInstance().getIoService().isRespondingToPings();
            registered = Collect.getInstance().getIoService().isRegistered();

            return null;
        }

        @Override
        protected void onPreExecute() {
            mProgressLoading.setText("Connecting");
        }

        @Override
        protected void onPostExecute(Void nothing) {
            if (upgradeFailed) {
                showDialog(DIALOG_UPGRADE_FAILED);
                return;
            }

            if (pinged) {
                if (registered) {
                    if (Collect.getInstance().getInformOnlineState().isExpired()) {
                        showDialog(DIALOG_EXPIRED);
                    } else {
                        mProgressLoading.setText("Starting Database");
                        startCouch();
                    }
                } else {
                    startActivity(new Intent(getApplicationContext(), ClientRegistrationActivity.class));
                    finish();
                }
            } else {
                if (registered) {
                    if (Collect.getInstance().getInformOnlineState().isExpired()) {
                        showDialog(DIALOG_EXPIRED_CANNOT_CONNECT);
                    } else {
                        if (Collect.getInstance().getInformOnlineState().isOfflineModeEnabled())
                            showDialog(DIALOG_UNABLE_TO_CONNECT_OFFLINE_ENABLED);
                        else
                            showDialog(DIALOG_UNABLE_TO_CONNECT_OFFLINE_DISABLED);
                    }
                } else {
                    showDialog(DIALOG_UNABLE_TO_REGISTER);
                }
            }
        }

        /*
         * Test for the old way of packaging Couch (e.g., sdcard-based install) and perform steps
         * to bring the environment up-to-date for the new Couch install
         */
        private void couchPackageUpgradePath() throws IOException {
            /*
             * Only copy the actual Couch databases (view index directories named .dbname_design and the
             * contents thereof will be automatically regenerated)
             */
            class DbFilesFilter implements FilenameFilter {
                @Override
                public boolean accept(File dir, String filename) {
                    if (filename.startsWith("db_") && filename.endsWith(".couch"))
                        return true;

                    return false;
                }
            }

            // Are the old Couch & Erlang directories present?
            if (!new File(FileUtilsExtended.EXTERNAL_COUCH).exists()
                    && !new File(FileUtilsExtended.EXTERNAL_ERLANG).exists())
                return;

            if (Collect.Log.DEBUG)
                Log.d(Collect.LOGTAG, t + "old CouchDB environment detected; about to execute upgrade path");

            // Get a list of Couch database files
            FilenameFilter filter = new DbFilesFilter();
            String[] dbFiles = new File(FileUtilsExtended.EXTERNAL_COUCH, "/var/lib/couchdb").list(filter);

            // Move database and design files to the new location
            if (dbFiles == null) {
                if (Collect.Log.DEBUG)
                    Log.d(Collect.LOGTAG, t + "no databases to move");
            } else {
                for (String file : dbFiles) {
                    if (Collect.Log.VERBOSE)
                        Log.v(Collect.LOGTAG, t + "about to copy " + file + " to new location");
                    File f = new File(FileUtilsExtended.EXTERNAL_COUCH, "/var/lib/couchdb/" + file);
                    org.apache.commons.io.FileUtils.copyFileToDirectory(f, new File(FileUtilsExtended.EXTERNAL_DB));
                }
            }

            // Remove couchdb, erlang and other files on sdcard data storage
            CouchInstaller.deleteDirectory(new File(FileUtilsExtended.EXTERNAL_COUCH));
            CouchInstaller.deleteDirectory(new File(FileUtilsExtended.EXTERNAL_ERLANG));
            CouchInstaller.deleteDirectory(new File(FileUtilsExtended.EXTERNAL_FILES));

            // Remove couchdb & erlang on internal data storage
            CouchInstaller.appNamespace = LauncherActivity.this.getApplication().getPackageName();
            CouchInstaller.deleteDirectory(new File(CouchInstaller.dataPath() + "/couchdb"));
            CouchInstaller.deleteDirectory(new File(CouchInstaller.dataPath() + "/erlang"));
        }
    }

    private void displaySplash() {
        // Don't show the splash screen if this app appears to be registered
        if (PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
                .getString(InformOnlineState.DEVICE_ID, null) instanceof String) {
            return;
        }

        // Fetch the splash screen Drawable
        Drawable image = null;

        try {
            // Attempt to load the configured default splash screen
            // The following code only works in 1.6+
            // BitmapDrawable bitImage = new BitmapDrawable(getResources(), FileUtils.SPLASH_SCREEN_FILE_PATH);
            BitmapDrawable bitImage = new BitmapDrawable(
                    FileUtilsExtended.EXTERNAL_FILES + File.separator + FileUtilsExtended.SPLASH_SCREEN_FILE);

            if (bitImage.getBitmap() != null && bitImage.getIntrinsicHeight() > 0
                    && bitImage.getIntrinsicWidth() > 0) {
                image = bitImage;
            }
        } catch (Exception e) {
            // TODO: log exception for debugging?
        }

        // TODO: rework
        if (image == null) {
            // no splash provided...
            //            if (FileUtils.storageReady() && !((new File(FileUtils.DEFAULT_CONFIG_PATH)).exists())) {
            // Show the built-in splash image if the config directory 
            // does not exist. Otherwise, suppress the icon.
            image = getResources().getDrawable(R.drawable.gc_color);
            //            }

            if (image == null)
                return;
        }

        // Create ImageView to hold the Drawable...
        ImageView view = new ImageView(getApplicationContext());

        // Initialise it with Drawable and full-screen layout parameters
        view.setImageDrawable(image);

        int width = getWindowManager().getDefaultDisplay().getWidth();
        int height = getWindowManager().getDefaultDisplay().getHeight();

        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(width, height, 0);

        view.setLayoutParams(lp);
        view.setScaleType(ScaleType.CENTER);
        view.setBackgroundColor(Color.WHITE);

        // And wrap the image view in a frame layout so that the full-screen layout parameters are honoured
        FrameLayout layout = new FrameLayout(getApplicationContext());
        layout.addView(view);

        // Create the toast and set the view to be that of the FrameLayout
        mSplashToast = Toast.makeText(getApplicationContext(), "splash screen", Toast.LENGTH_LONG);
        mSplashToast.setView(layout);
        mSplashToast.setGravity(Gravity.CENTER, 0, 0);
        mSplashToast.show();
    }

    private void exitApplication() {
        System.runFinalizersOnExit(true);
        System.exit(0);
    }

    // Restart this activity, optionally requesting a complete restart
    private void restartActivity(boolean fullRestart) {
        Intent i = new Intent(getApplicationContext(), LauncherActivity.class);

        // If the user wants a full restart then request reinitialization of the IO service
        if (fullRestart)
            i.putExtra(KEY_REINIT_IOSERVICE, true);

        startActivity(i);
        finish();
    }

    private void startCouch() {
        Collect.getInstance()
                .setCouchService(CouchDB.getService(getBaseContext(), null, "release-1.0.2-2", mCouchCallback));
    }
}