com.smartnsoft.droid4me.app.ActivityController.java Source code

Java tutorial

Introduction

Here is the source code for com.smartnsoft.droid4me.app.ActivityController.java

Source

/*
 * (C) Copyright 2009-2011 Smart&Soft SAS (http://www.smartnsoft.com/) and contributors.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser General Public License
 * (LGPL) version 2.1 which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl.html
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * Contributors:
 *     E2M - initial API and implementation
 *     Smart&Soft - initial API and implementation
 */

package com.smartnsoft.droid4me.app;

import java.io.InterruptedIOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;

import javax.net.ssl.SSLException;

import org.apache.http.NoHttpResponseException;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcelable;
import android.view.Window;

import com.smartnsoft.droid4me.LifeCycle;
import com.smartnsoft.droid4me.LifeCycle.BusinessObjectUnavailableException;
import com.smartnsoft.droid4me.log.Logger;
import com.smartnsoft.droid4me.log.LoggerFactory;

/**
 * Is responsible for intercepting an activity starting and redirect it to a prerequisite one if necessary, and for handling globally exceptions.
 * 
 * <p>
 * Everything described here which involves the {@link Activity activities}, is applicable provided the activity is a {@link Smartable}.
 * </p>
 * 
 * <p>
 * It is also a container for multiple interfaces relative to its architecture.
 * </p>
 * 
 * @author douard Mercier
 * @since 2009.04.28
 */
public class ActivityController {

    /**
     * An interface which is requested when a new {@link Activity} is bound to be {@link Context#startActivity(Intent) started}.
     * 
     * <p>
     * The redirector acts as a controller over the activities starting phase: if an activity should be started before another one is really
     * {@link Activity#onResume() active}, this is the right place to handle this at runtime.
     * </p>
     * 
     * <p>
     * This component is especially useful when ones need to make sure that an {@link Activity} has actually been submitted to the end-user before
     * resuming a workflow. The common cases are the traditional application splash screen, or a signin/signup process.
     * </p>
     * 
     * @see ActivityController#registerRedirector(Redirector)
     */
    public interface Redirector {

        /**
         * Will be invoked by the {@link ActivityController#needsRedirection(Activity) framework}, in order to know whether an {@link Activity} should be
         * started instead of the provided one, which is supposed to have just {@link Activity#onCreate(Bundle) started}, or when the
         * {@link Activity#onNewIntent()} method is invoked. However, the method will be not been invoked when those methods are invoked due to a
         * {@link Activity#onConfigurationChanged(android.content.res.Configuration) configuration change}.
         * 
         * <p>
         * Caution: if an exception is thrown during the method execution, the application will crash!
         * </p>
         * 
         * @param activity
         *          the activity which is bound to be displayed
         * @return {@code null} if and only if nothing is to be done, i.e. no activity should be started instead. Otherwise, the given intent will be
         *         executed: in that case, the provided activity {@Activity#finish finishes}
         * @see ActivityController#needsRedirection(Activity)
         */
        Intent getRedirection(Activity activity);

    }

    /**
     * An empty interface which should be used as a marker on an {@link Activity}, which does not want to be requested by the
     * {@link ActivityController.Redirector}.
     * 
     * <p>
     * When an {@link Activity} implements this interface, the {@link ActivityController.Redirector#getRedirection(Activity)} method will not be
     * invoked.
     * </p>
     * 
     * @since 2012.04.11
     * @see ActivityController#needsRedirection(Activity)
     * @see ActivityController.EscapeToRedirectorAnnotation
     * @deprecated now use the {@link ActivityController.EscapeToRedirectorAnnotation} annotation instead
     */
    @Deprecated
    public interface EscapeToRedirector {
    }

    /**
     * An annotation which offers the same effect as the {@link ActivityController.EscapeToRedirector} interface.
     * 
     * @since 2014.04.26
     * @see ActivityController#needsRedirection(Activity)
     * @see ActivityController.EscapeToRedirector
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public static @interface EscapeToRedirectorAnnotation {

    }

    /**
     * An interface responsible for providing Android system services, given their name. It enables to override in one place all the {@code Activity}
     * system services.
     * 
     * @since 2012.02.12
     */
    public interface SystemServiceProvider {

        /**
         * Returns the handle to a system-level service by name. The class of the returned object varies by the requested name.
         * 
         * @param activity
         *          the {@link Activity} asking for the service
         * @param name
         *          the name of the desired service
         * @param defaultService
         *          the provided {@code activity} default system service (retrieved by invoking the {@link Activity#getSystemService(String)} method)
         * @return the desired service, or {@code null} if no service corresponding to the provided {@code name} is available nor exists
         * @see {@link Activity#getSystemService(String)}
         */
        Object getSystemService(Activity activity, String name, Object defaultService);

    }

    /**
     * An interface which is queried during the various life cycle events of a {@link LifeCycle}.
     * 
     * <p>
     * An interceptor is the ideal place for centralizing in one place many of the {@link Activity}/{@link android.app.Fragment} entity life cycle
     * events.
     * </p>
     * 
     * @see ActivityController#registerInterceptor(Interceptor)
     */
    public interface Interceptor {

        /**
         * Defines all the events handled by the {@link ActivityController.Interceptor}.
         */
        public static enum InterceptorEvent {
            /**
             * Called during the {@link Activity#onCreate()} / {@link android.app.Fragment#onCreate()} method, before the Android built-in super method
             * {@link Activity#onCreate} method is invoked.
             * 
             * <p>
             * This is an ideal place where to {@link Window#requestFeature() request for window features}.
             * </p>
             */
            onSuperCreateBefore,
            /**
             * Called during the {@link Activity#onCreate()} / {@link android.app.Fragment#onCreate()} method, at the beginning of the method, but after the
             * parent's call, provided no {@link ActivityController.Redirector activity redirection} is requested.
             */
            onCreate,
            /**
             * Called during the {@link Activity#onCreate()} / {@link android.app.Fragment#onCreate()} method, at the very end of the method, after the
             * parent's call, provided no {@link ActivityController.Redirector activity redirection} is requested.
             */
            onCreateDone,
            /**
             * <b>Only applies to {@link Activity} entities!</b>.Called during the {@link Activity#onPostCreate()} method, at the beginning of the method,
             * but after the parent's call, provided no {@link ActivityController.Redirector activity redirection} is requested.
             */
            onPostCreate,
            /**
             * <b>Only applies to {@link Activity} entities!</b>.Called at the end of the {@link Activity#onContentChanged()} method execution, but after
             * the parent's call, provided no {@link ActivityController.Redirector activity redirection} is requested.
             */
            onContentChanged,
            /**
             * Called during the {@link Activity#onStart} / {@link android.app.Fragment#onStart()} method, at the beginning of the method, but after the
             * parent's call, provided no {@link ActivityController.Redirector activity redirection} is requested and that the instance has not been
             * recreated due a configuration change.
             */
            onStart,
            /**
             * <b>Only applies to {@link Activity} entities!</b>.Called during the {@link Activity#onRestart()} method, after the parent's call, provided no
             * {@link ActivityController.Redirector activity redirection} is requested and that the instance has not been recreated due a configuration
             * change.
             */
            onRestart,
            /**
             * Called during the {@link Activity#onResume()} / {@link android.app.Fragment#onResume()} method, at the beginning of the method, but after the
             * parent's call, provided no {@link ActivityController.Redirector activity redirection} is requested.
             */
            onResume,
            /**
             * <b>Only applies to {@link Activity} entities!</b>.Called during the {@link Activity#onPostResume()} method, at the beginning of the method,
             * but after the parent's call, provided no {@link ActivityController.Redirector activity redirection} is requested.
             */
            onPostResume,
            /**
             * Called during the {@link Activity#onPause()} / {@link android.app.Fragment#onPause()} method, at the beginning of the method, but after the
             * parent's call, provided no {@link ActivityController.Redirector activity redirection} is requested.
             */
            onPause,
            /**
             * Called during the {@link Activity#onStop()} / {@link android.app.Fragment#onPause()} method, at the beginning of the method, before the
             * parent's call.
             */
            onStop,
            /**
             * Called just after the {@link LifeCycle#onFulfillDisplayObjects} method.
             */
            onFulfillDisplayObjectsDone,
            /**
             * Called just after the {@link LifeCycle#onSynchronizeDisplayObjects} method.
             */
            onSynchronizeDisplayObjectsDone,
            /**
             * Called during the {@link Activity#onDestroy()} / {@link android.app.Fragment#onDestroy()} method, at the very end of the method.
             */
            onDestroy
        }

        /**
         * A logger which may be used by the classes implementing this interface.
         */
        public static final Logger log = LoggerFactory.getInstance(Interceptor.class);

        /**
         * Invoked every time a new event occurs on the provided {@code activity}/{@code component}. For instance, this is an ideal for logging
         * application usage analytics.
         * 
         * <p>
         * The framework ensures that this method will be invoked from the UI thread, hence the method implementation should last a very short time!
         * <p>
         * 
         * <p>
         * Caution: if an exception is thrown during the method execution, the application will crash!
         * </p>
         * 
         * @param activity
         *          the activity on which a life cycle event occurs ; cannot be {@code null}
         * @param component
         *          the component on which the life cycle event occurs ; may be {@code null}
         * @param event
         *          the event that has just happened
         * 
         * @see ActivityController#onLifeCycleEvent()
         */
        void onLifeCycleEvent(Activity activity, Object component,
                ActivityController.Interceptor.InterceptorEvent event);

    }

    /**
     * Defines and splits the handling of various exceptions in a single place. This handler will be invoked once it has been
     * {@link ActivityController#registerExceptionHandler(ExceptionHandler) registered}.
     * 
     * <p>
     * The exception handler will be invoked at runtime when an exception is thrown and is not handled. You do not need to log the exception, because
     * the {@link ActivityController} already takes care of logging it, before invoking the current interface methods.
     * </p>
     * 
     * @see ActivityController#registerExceptionHandler(ExceptionHandler)
     */
    public interface ExceptionHandler {

        /**
         * Is invoked whenever the {@link LifeCycle#onRetrieveBusinessObjects()} throws an exception.
         * 
         * <p>
         * Warning, it is not ensured that this method will be invoked from the UI thread!
         * </p>
         * 
         * @param activity
         *          the activity that issued the exception, and which is ensured not to be {@link Activity#finish() finishing} ; cannot be {@code null}
         * @param component
         *          the component that issued the exception ; may be {@code null}
         * @param exception
         *          the exception that has been thrown
         * @return {@code true} if the handler has actually handled the exception: this indicates to the framework that it does not need to investigate
         *         for a further exception handler anymore
         */
        boolean onBusinessObjectAvailableException(Activity activity, Object component,
                BusinessObjectUnavailableException exception);

        /**
         * Is invoked whenever an activity implementing {@link LifeCycle} throws an unexpected exception outside from the
         * {@link LifeCycle#onRetrieveBusinessObjects()} method.
         * 
         * <p>
         * This method serves as a fallback on the framework, in order to handle gracefully exceptions and prevent the application from crashing.
         * </p>
         * 
         * <p>
         * Warning, it is not ensured that this method will be invoked from the UI thread!
         * </p>
         * 
         * @param activity
         *          the activity that issued the exception ; cannot be {@code null}
         * @param component
         *          the component that issued the exception ; may be {@code null}
         * @param throwable
         *          the exception that has been triggered
         * @return {@code true} if the handler has actually handled the exception: this indicates to the framework that it does not need to investigate
         *         for a further exception handler anymore
         */
        boolean onActivityException(Activity activity, Object component, Throwable throwable);

        /**
         * Is invoked whenever a handled exception is thrown with a non-{@link Activity} / {@link android.app.Fragment} {@link Context context}.
         * 
         * <p>
         * This method serves as a fallback on the framework, in order to handle gracefully exceptions and prevent the application from crashing.
         * </p>
         * 
         * <p>
         * Warning, it is not ensured that this method will be invoked from the UI thread!
         * </p>
         * 
         * @param isRecoverable
         *          indicates whether the application is about to crash when the exception has been triggered
         * @param context
         *          the context that issued the exception
         * @param throwable
         *          the exception that has been triggered
         * @return {@code true} if the handler has actually handled the exception: this indicates to the framework that it does not need to investigate
         *         for a further exception handler anymore
         */
        boolean onContextException(boolean isRecoverable, Context context, Throwable throwable);

        /**
         * Is invoked whenever a handled exception is thrown outside from an available {@link Context context}.
         * 
         * <p>
         * This method serves as a fallback on the framework, in order to handle gracefully exceptions and prevent the application from crashing.
         * </p>
         * 
         * <p>
         * Warning, it is not ensured that this method will be invoked from the UI thread!
         * </p>
         * 
         * @param isRecoverable
         *          indicates whether the application is about to crash when the exception has been triggered
         * @param throwable
         *          the exception that has been triggered
         * @return {@code true} if the handler has actually handled the exception: this indicates to the framework that it does not need to investigate
         *         for a further exception handler anymore
         */
        boolean onException(boolean isRecoverable, Throwable throwable);

    }

    /**
     * Responsible for analyzing issues resulting from {@link Throwable} entities.
     * 
     * @since 2013.12.23
     * 
     */
    public static abstract class IssueAnalyzer {

        public final static class IssueContext {

            private final static String SPLITTER = ";";

            public final String applicationName;

            public final String applicationVersionName;

            public final int applicationVersionCode;

            public final String deviceModel;

            public final String firmwareVersion;

            public final String buildNumber;

            public IssueContext(String string) {
                final String[] tokens = string.split(IssueContext.SPLITTER);
                int index = 0;
                applicationName = tokens[index++];
                applicationVersionName = tokens[index++];
                applicationVersionCode = Integer.parseInt(tokens[index++]);
                deviceModel = tokens[index++];
                firmwareVersion = tokens[index++];
                buildNumber = tokens[index++];
            }

            public IssueContext(Context context) {
                applicationName = context.getString(context.getApplicationInfo().labelRes);
                PackageInfo packageInfo = null;
                try {
                    packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(),
                            PackageManager.GET_META_DATA);
                } catch (NameNotFoundException exception) {
                    // Cannot happen
                }
                applicationVersionName = packageInfo == null ? "" : packageInfo.versionName;
                applicationVersionCode = packageInfo == null ? -1 : packageInfo.versionCode;
                deviceModel = Build.MODEL;
                firmwareVersion = Build.VERSION.RELEASE;
                buildNumber = Build.DISPLAY;
            }

            @Override
            public String toString() {
                return applicationName + IssueContext.SPLITTER + applicationVersionName + IssueContext.SPLITTER
                        + applicationVersionCode + IssueContext.SPLITTER + deviceModel + IssueContext.SPLITTER
                        + firmwareVersion + IssueContext.SPLITTER + buildNumber;
            }

            public String toHumanString() {
                final StringBuilder sb = new StringBuilder();
                sb.append("applicationName = ").append(applicationName).append("\n");
                sb.append("applicationVersionName = ").append(applicationVersionName).append("\n");
                sb.append("applicationVersionCode = ").append(applicationVersionCode).append("\n");
                sb.append("deviceModel = ").append(deviceModel).append("\n");
                sb.append("firmwareVersion = ").append(firmwareVersion).append("\n");
                sb.append("buildNumber = ").append(buildNumber).append("\n");
                return sb.toString();
            }

        }

        protected final static Logger log = LoggerFactory.getInstance(IssueAnalyzer.class);

        /**
         * Attempts to find a specific exception in the provided exception by iterating over the causes, starting with the provided exception itself.
         * 
         * @param throwable
         *          the exception to be inspected
         * @param exceptionClass
         *          a list of exception classes to look after
         * @return {@code null} if and only one of the provided exception classes has not been detected ; the matching cause otherwise
         */
        @SuppressWarnings({ "unchecked", "rawtypes" })
        public static final Throwable searchForCause(Throwable throwable, Class... exceptionClass) {
            Throwable newThrowable = throwable;
            Throwable cause = throwable;
            // We investigate over the whole causes stack
            while (cause != null) {
                for (Class<? extends Throwable> anExceptionClass : exceptionClass) {
                    final Class<? extends Throwable> causeClass = cause.getClass();
                    if (causeClass == anExceptionClass) {
                        return cause;
                    }
                    // We scan the cause class hierarchy
                    Class<?> superclass = causeClass.getSuperclass();
                    while (superclass != null) {
                        if (superclass == anExceptionClass) {
                            return cause;
                        }
                        superclass = superclass.getSuperclass();
                    }
                }
                // It seems that when there are no more causes, the exception itself is returned as a cause: stupid implementation!
                if (newThrowable.getCause() == newThrowable) {
                    break;
                }
                newThrowable = cause;
                cause = newThrowable.getCause();
            }
            return null;
        }

        /**
         * @param throwable
         *          the exception to investigate
         * @return {@code true} if and only if the exception results from a connectivity issue by inspecting its causes tree
         */
        public static boolean isAConnectivityProblem(Throwable throwable) {
            return ActivityController.IssueAnalyzer.searchForCause(throwable, UnknownHostException.class,
                    SocketException.class, SocketTimeoutException.class, InterruptedIOException.class,
                    NoHttpResponseException.class, SSLException.class) != null;
        }

        /**
         * @param throwable
         *          the exception to investigate
         * @return {@code true} if and only if the exception results from a memory saturation issue (i.e. a {@link OutOfMemoryError} exception) by
         *         inspecting its causes tree
         */
        public static boolean isAMemoryProblem(Throwable throwable) {
            return ActivityController.IssueAnalyzer.searchForCause(throwable, OutOfMemoryError.class) != null;
        }

        protected final Context context;

        /**
         * @param context
         *          should be an application {@link Context}
         */
        public IssueAnalyzer(Context context) {
            this.context = context;
        }

        /**
         * Is responsible for analyzing the provided exception, and indicates whether it has been handled.
         * 
         * @param throwable
         *          the issue to analyze
         * @return {@code true} if and only if the issue has actually been handled by the implementation
         */
        public abstract boolean handleIssue(Throwable throwable);

    }

    private static final Logger log = LoggerFactory.getInstance(ActivityController.class);

    /**
     * When a new activity is {@link Context#startActivity(Intent) started} because of a redirection, the newly started activity will receive the
     * initial activity {@link Intent} through this {@link Parcelable} key.
     * 
     * @see #needsRedirection(Activity)
     * @see #registerInterceptor(ActivityController. Interceptor)
     */
    public static final String CALLING_INTENT = "com.smartnsoft.droid4me.callingIntent";

    /**
     * A singleton pattern is available for the moment.
     */
    private static volatile ActivityController instance;

    /**
     * The only way to access to the activity controller.
     */
    // Implements a "double-checked locking" pattern.
    public static ActivityController getInstance() {
        if (ActivityController.instance == null) {
            synchronized (ActivityController.class) {
                if (ActivityController.instance == null) {
                    ActivityController.instance = new ActivityController();
                }
            }
        }
        return ActivityController.instance;
    }

    private ActivityController.Redirector redirector;

    private ActivityController.SystemServiceProvider systemServiceProvider;

    private ActivityController.Interceptor interceptor;

    private ActivityController.ExceptionHandler exceptionHandler;

    /**
     * No one else than the framework should create such an instance.
     */
    private ActivityController() {
    }

    /**
     * Attempts to decode from the provided {@code activity} the original {@code Intent} that was
     * 
     * @param activity
     *          the Activity whose Intent will be analyzed
     * @return an Intent that may be {@link Activity#startActivity(Intent) started} if the provided {@code activity} actually contains a reference to
     *         another {@link Activity} ; {@code null} otherwise
     * @see ActivityController#CALLING_INTENT
     * @see #needsRedirection(Activity)
     * @see #registerInterceptor(ActivityController. Interceptor)
     */
    public static Intent extractCallingIntent(Activity activity) {
        return activity.getIntent().getParcelableExtra(ActivityController.CALLING_INTENT);
    }

    /**
     * Remembers the system service provider that will be used by the framework, for overriding the {@link Activity#getSystemService(String)} method.
     * 
     * @param systemServiceProvider
     *          the system service provider which will be invoked at runtime, when an {@link Activity} asks for a service ; if {@code null}, the
     *          {@link Activity} default service will be used
     */
    public void registerSystemServiceProvider(ActivityController.SystemServiceProvider systemServiceProvider) {
        this.systemServiceProvider = systemServiceProvider;
    }

    /**
     * Remembers the activity redirector that will be used by the framework, before {@link Context#startActivity(Intent) starting} a new
     * {@link Activity}.
     * 
     * @param redirector
     *          the redirector that will be requested at runtime, when a new activity is being started; if {@code null}, no redirection mechanism will
     *          be set up
     */
    public void registerRedirector(ActivityController.Redirector redirector) {
        this.redirector = redirector;
    }

    /**
     * Remembers the activity interceptor that will be used by the framework, on every {@link ActivityController.Interceptor.InterceptorEvent event}
     * during the underlying {@link Activity} life cycle.
     * 
     * @param interceptor
     *          the interceptor that will be invoked at runtime, on every event; if {@code null}, no interception mechanism will be used
     */
    public void registerInterceptor(ActivityController.Interceptor interceptor) {
        this.interceptor = interceptor;
    }

    /**
     * Is responsible for returning a system service, just like the {@link Context#getSystemService(String)} method does.
     * 
     * @param activity
     *          the activity asking for a system service
     * @param name
     *          the name of the desired service
     * @param defaultService
     *          the {@code activity} default service
     * @return the service or {@code null} if the name does not exist
     * @see #registerSystemServiceProvider(ActivityController.SystemServiceProvider)
     */
    public Object getSystemService(Activity activity, String name, Object defaultService) {
        if (systemServiceProvider != null) {
            return systemServiceProvider.getSystemService(activity, name, defaultService);
        }
        return defaultService;
    }

    /**
     * Gives access to the currently registered {@link ActivityController.ExceptionHandler}.
     * 
     * @return the currently registered exception handler ; may be {@code null}, which is the default status
     * @see #registerExceptionHandler(ActivityController.ExceptionHandler)
     */
    public ActivityController.ExceptionHandler getExceptionHandler() {
        return exceptionHandler;
    }

    /**
     * Remembers the exception handler that will be used by the framework.
     * 
     * @param exceptionHandler
     *          the handler that will be invoked in case of exception; if {@code null}, no exception handler will be used
     * @see #getExceptionHandler()
     */
    public synchronized void registerExceptionHandler(ActivityController.ExceptionHandler exceptionHandler) {
        this.exceptionHandler = exceptionHandler;
    }

    /**
     * Is invoked by the framework every time a life cycle event occurs for the provided activity. You should not invoke that method yourself!
     * 
     * <p>
     * Note that the method is synchronized, which means that the previous call will block the next one, if no thread is spawn.
     * </p>
     * 
     * @param activity
     *          the activity which is involved with the event : cannot be {@code null}
     * @param component
     *          the component the event occurs on ; may be {code null}
     * @param event
     *          the event that has just happened for that activity
     */
    public synchronized void onLifeCycleEvent(Activity activity, Object component,
            ActivityController.Interceptor.InterceptorEvent event) {
        if (interceptor == null) {
            return;
        }
        interceptor.onLifeCycleEvent(activity, component, event);
    }

    /**
     * Dispatches the exception to the {@link ActivityController.ExceptionHandler}, and invokes the right method depending on its nature.
     * 
     * <p>
     * The framework is responsible for invoking that method every time an unhandled exception is thrown. If no
     * {@link ActivityController#registerExceptionHandler(ExceptionHandler) exception handler is registered}, the exception will be only logged, and the
     * method will return {@code false}.
     * </p>
     * 
     * <p>
     * Note that this method is {@code synchronized}, which prevents it from being invoking while it is already being executed, and which involves that
     * only one {@link Throwable} may be handled at the same time.
     * </p>
     * 
     * @param isRecoverable
     *          indicates whether the application is about to crash when the exception has been triggered
     * @param context
     *          the context that originated the exception ; may be {@code null}
     * @param component
     *          when not {@code null}, this will be the {@link android.app.Fragment} the exception has been thrown from
     * @param throwable
     *          the reported exception
     * @return {@code true} if the exception has been handled ; in particular, if no {@link ActivityController#getExceptionHandler() exception handled
     *         has been set}, returns {@code false}
     */
    public synchronized boolean handleException(boolean isRecoverable, Context context, Object component,
            Throwable throwable) {
        if (exceptionHandler == null) {
            if (log.isWarnEnabled()) {
                log.warn(
                        "Detected an exception which will not be handled during the processing of the context with name '"
                                + (context == null ? "null" : context.getClass().getName()) + "'",
                        throwable);
            }
            return false;
        }
        final Activity activity;
        if (context instanceof Activity) {
            activity = (Activity) context;
        } else {
            activity = null;
        }
        try {
            if (activity != null && throwable instanceof BusinessObjectUnavailableException) {
                // Should only occur with a non-null activity
                final BusinessObjectUnavailableException exception = (BusinessObjectUnavailableException) throwable;
                if (log.isWarnEnabled()) {
                    log.warn(
                            "Caught an exception during the retrieval of the business objects from the activity from class with name '"
                                    + activity.getClass().getName() + "'",
                            exception);
                }
                // We do nothing if the activity is dying
                if (activity != null && activity.isFinishing() == true) {
                    return true;
                }
                return exceptionHandler.onBusinessObjectAvailableException(activity, component, exception);
            } else {
                if (log.isWarnEnabled()) {
                    log.warn(
                            "Caught an exception during the processing of "
                                    + (context == null ? "a null Context"
                                            : "the Context from class with name '" + context.getClass().getName())
                                    + "'",
                            throwable);
                }
                // For this special case, we ignore the case when the activity is dying
                if (activity != null) {
                    return exceptionHandler.onActivityException(activity, component, throwable);
                } else if (context != null) {
                    return exceptionHandler.onContextException(isRecoverable, context, throwable);
                } else {
                    return exceptionHandler.onException(isRecoverable, throwable);
                }
            }
        } catch (Throwable otherThrowable) {
            // Just to make sure that handled exceptions do not trigger un-handled exceptions on their turn ;(
            if (log.isErrorEnabled()) {
                log.error(
                        "An error occurred while attempting to handle an exception coming from "
                                + (context == null ? "a null Context"
                                        : "the Context from class with name '" + context.getClass().getName())
                                + "'",
                        otherThrowable);
            }
            return false;
        }
    }

    /**
     * Indicates whether a redirection is required before letting the activity continue its life cycle. It launches the redirected {@link Activity} if a
     * redirection is need, and provide to its {@link Intent} the initial activity {@link Intent} trough the extra {@link Parcelable}
     * {@link ActivityController#CALLING_INTENT} key.
     * 
     * <p>
     * If the provided {@code activity} implements the {@link ActivityController.EscapeToRedirector} interface or exposes the
     * {@link ActivityController.EscapeToRedirectorAnnotation} annotation, the method returns {@code false}.
     * </p>
     * 
     * <p>
     * Note that this method does not need to be marked as {@code synchronized}, because it is supposed to be invoked systematically from the UI thread.
     * </p>
     * 
     * @param activity
     *          the activity which is being proved against the {@link ActivityController.Redirector}
     * @return {@code true} if and only if the given activity should be paused (or ended) and if another activity should be launched instead through the
     *         {@link Activity#startActivity(Intent)} method
     * @see ActivityController#extractCallingIntent(Activity)
     * @see ActivityController.Redirector#getRedirection(Activity)
     * @see ActivityController.EscapeToRedirector
     * @see ActivityController.EscapeToRedirectorAnnotation
     */
    public boolean needsRedirection(Activity activity) {
        if (redirector == null) {
            return false;
        }
        if (activity instanceof ActivityController.EscapeToRedirector || activity.getClass()
                .getAnnotation(ActivityController.EscapeToRedirectorAnnotation.class) != null) {
            if (log.isDebugEnabled()) {
                log.debug("The Activity with class '" + activity.getClass().getName()
                        + "' is escaped regarding the Redirector");
            }
            return false;
        }
        final Intent intent = redirector.getRedirection(activity);
        if (intent == null) {
            return false;
        }
        if (log.isDebugEnabled()) {
            log.debug("A redirection is needed");
        }

        // We redirect to the right Activity
        {
            // We consider the parent activity in case it is embedded (like in an ActivityGroup)
            final Intent formerIntent = activity.getParent() != null ? activity.getParent().getIntent()
                    : activity.getIntent();
            intent.putExtra(ActivityController.CALLING_INTENT, formerIntent);
            // Disables the fact that the new started activity should belong to the tasks history and from the recent tasks
            // intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
            // intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
            activity.startActivity(intent);
        }

        // We now finish the redirected Activity
        activity.finish();

        return true;
    }

}