org.ymkm.lib.controller.support.ControlledFragment.java Source code

Java tutorial

Introduction

Here is the source code for org.ymkm.lib.controller.support.ControlledFragment.java

Source

/*******************************************************************************
 * Copyright 2013 Yoann Mikami <yoann@ymkm.org>
 * 
 * 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 org.ymkm.lib.controller.support;

import org.ymkm.lib.controller.core.ControllableFragmentException;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.v4.app.Fragment;

/**
 * Defines a {@link Fragment} that is controllable by a {@link FragmentControllerApplication}.
 * 
 */
public abstract class ControlledFragment extends Fragment implements ControllableFragment {

    /**
     * Instantiates a new {@linkplain ControlledFragment} of specified
     * {@code Class}, whose handler will run in the specified Looper
     * 
     * @param f
     *            the subclass of {@linkplain ControlledFragment} to instantiate
     * @param runsInOwnThread
     *            {@code true} if a new thread should be created for this
     *            fragment, {@code false} to let it run in the current thread
     * @return A new instance of {@linkplain ControllableFragment}
     * @throws ControllableFragmentException
     *             if instantiation failed
     */
    public static ControllableFragment createFragment(Class<? extends ControllableFragment> f,
            boolean runsInOwnThread) throws ControllableFragmentException {
        return createFragment(f, runsInOwnThread, new Bundle());
    }

    /**
     * Instantiates a new {@linkplain ControllableFragment} of specified
     * {@code Class}.
     * <p>
     * The {@link Handler} for this fragment will run in the thread of the
     * context that created it.
     * </p>
     * 
     * @param f
     *            the subclass of {@linkplain ControlledFragment} to instantiate
     * @return A new instance of {@linkplain ControllableFragment}
     * @throws ControllableFragmentException
     *             if instantiation failed
     */
    public static ControllableFragment createFragment(Class<? extends ControllableFragment> f)
            throws ControllableFragmentException {
        return createFragment(f, false, new Bundle());
    }

    /**
     * Instantiates a new {@linkplain ControllableFragment} of specified
     * {@code Class}.
     * <p>
     * The {@link Handler} for this fragment will run in the thread of the
     * context that created it.
     * </p>
     * 
     * @param f
     *            the subclass of {@linkplain ControlledFragment} to instantiate
     * @param args
     *            optional arguments to pass to the fragment as a bundle
     * @return A new instance of {@linkplain ControllableFragment}
     * @throws ControllableFragmentException
     *             if instantiation failed
     */
    public static ControllableFragment createFragment(Class<? extends ControllableFragment> f, Bundle args)
            throws ControllableFragmentException {
        assert (null != args);
        return createFragment(f, false, args);
    }

    /**
     * Instantiates a new {@linkplain ControllableFragment} of specified
     * {@code Class}, whose handler will run in the specified Looper
     * 
     * @param f
     *            the subclass of {@linkplain ControlledFragment} to instantiate
     * @param runsInOwnThread
     *            {@code true} if a new thread should be created for this
     *            fragment, {@code false} to let it run in the current thread
     * @return A new instance of {@linkplain ControllableFragment}
     * @throws ControllableFragmentException
     *             if instantiation failed
     */
    public static ControllableFragment createFragment(Class<? extends ControllableFragment> f,
            boolean runsInOwnThread, Bundle args) throws ControllableFragmentException {
        assert (null != args);
        try {
            ControlledFragment fragment = (ControlledFragment) f.newInstance();
            args.putBoolean("__new_thread__", runsInOwnThread);
            fragment.setArguments(args);
            return fragment;
        } catch (ClassCastException e) {
            e.printStackTrace();
            throw new ControllableFragmentException(e.getMessage());
        } catch (java.lang.InstantiationException e) {
            e.printStackTrace();
            throw new ControllableFragmentException(e.getMessage());
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new ControllableFragmentException(e.getMessage());
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Bundle b = getArguments();
        if (null == b) {
            b = new Bundle();
        }
        // Creates a new thread supplying a Looper for the Handler if creation
        // requested to run in a new thread.
        boolean newThread = b.getBoolean("__new_thread__",
                (null != savedInstanceState && savedInstanceState.getBoolean("__new_thread__", false)));
        mControllableName = b.getString("__controllable_name__");
        mControllableName = (null == mControllableName && null != savedInstanceState)
                ? savedInstanceState.getString("__controllable_name__")
                : mControllableName;
        mControlId = b.getInt("__control_id__", 0);
        mControlId = (0 == mControlId && null != savedInstanceState)
                ? savedInstanceState.getInt("__control_id__", 0)
                : mControlId;
        if (newThread) {
            synchronized (this) {
                if (null == _handlerThread) {
                    this._handlerThread = new HandlerThread(getFragmentName());
                    _handlerThread.start();
                }
            }
        } else {
            _handlerThread = null;
        }
        if (null != _handlerThread) {
            this.mHandler = new Handler(_handlerThread.getLooper(), getCallback());
        } else {
            this.mHandler = new Handler(getCallback());
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBoolean("__new_thread__", (null != _handlerThread));
        outState.putString("__controllable_name__", mControllableName);
        outState.putInt("__control_id__", mControlId);
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
    }

    @Override
    public void onDetach() {
        super.onDetach();
    }

    @Override
    public void onPause() {
        super.onPause();
        if (null != mControllerMessenger) {
            Message m = Message.obtain();
            m.what = FragmentController.MSG_DETACH_MESSAGE;
            m.arg1 = getControlId();
            m.obj = this;
            Bundle b = new Bundle();
            b.putString("controllableName", mControllableName);
            m.setData(b);
            try {
                mControllerMessenger.send(m);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        mControllerMessenger = null;
    }

    @Override
    public void onResume() {
        super.onResume();
        Activity activity = getActivity();
        if (activity instanceof ControllableActivity) {
            FragmentController controller = ((ControllableActivity) activity).getController();
            mControllerMessenger = controller.getMessenger();
        }

        if (null != mControllerMessenger) {
            Message m = Message.obtain();
            m.what = FragmentController.MSG_ATTACH_MESSAGE;
            m.arg1 = getControlId();
            m.obj = this;
            Bundle b = new Bundle();
            b.putString("controllableName", mControllableName);
            m.setData(b);
            try {
                mControllerMessenger.send(m);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void onStart() {
        super.onStart();
    }

    @Override
    public void onStop() {
        super.onStop();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        synchronized (this) {
            mHandler.removeCallbacksAndMessages(null);
            if (null != _handlerThread) {
                HandlerThread ht = _handlerThread;
                _handlerThread = null;
                ht.quit();
                ht.interrupt();
            }
            mHandler = null;
            mControllerMessenger = null;
        }
    }

    @Override
    public final Messenger getMessenger() {
        return new Messenger(mHandler);
    }

    /**
     * Returns true if the handler runs in a separate thread
     * 
     * @return true if it has its own thread for the inner Handler, false
     *         otherwise
     */
    @Override
    public final boolean hasOwnThread() {
        return (null != _handlerThread);
    }

    /**
     * Returns an instance of the {@link ControlledFragmentCallback} that can
     * process messages recognized by this fragment
     * 
     * @return the callback, or {@code null} in case of failure
     */
    private ControlledFragmentCallback getCallback() {
        if (null != doGetCallbackClass()) {
            try {
                return ControlledFragmentCallback.createCallback(doGetCallbackClass(), this);
            } catch (ControllableFragmentException e) {
                e.printStackTrace();
            }
        }
        return new ControlledFragmentCallback() {
            @Override
            public boolean handleMessage(Message msg) {
                return true;
            }
        };
    }

    /**
     * Returns a name for this fragment that can be used to identify the thread
     * the handler is running within
     * 
     * @return the fragment name
     */
    @Override
    public final String getFragmentName() {
        return doGetFragmentName();
    }

    /**
     * Sends a message to the controller via its supplied {@link Messenger}
     * <p>
     * A {@link ControlledFragment} can only communicate with the
     * {@link FragmentController}.
     * </p>
     * The sent message wraps the actual message that will be dispatched by the
     * controller, and has the following values :
     * <dl>
     * <dt>what</dt>
     * <dd>{@link FragmentController#MSG_DISPATCH_MESSAGE} => Dispatches the
     * message in the controller</dd>
     * <dt>arg1</dt>
     * <dd>The control ID assigned to the ControlledFragment that owns this
     * callback</dd>
     * <dt>arg2</dt>
     * <dd>The {@code what} passed as a parameter that needs to be dispatched</dd>
     * <dt>obj</dt>
     * <dd>{@link Message} that will get dispatched (the {@code what} will be
     * added to it)</dd>
     * </dl>
     * obj is a message instance that will eventually contain what passed as
     * parameter of this method.<br>
     * 
     * @param what
     *            message ID to get dispatched by the controller
     * @return {@code true} if message could be sent, {@code false} otherwise
     */
    @Override
    public final boolean sendToController(int what) {
        return sendToController(what, 0, 0, null);
    }

    /**
     * Sends a message to the controller via its supplied {@link Messenger}
     * <p>
     * A {@link ControlledFragment} can only communicate with the
     * {@link FragmentController}.
     * </p>
     * The sent message wraps the actual message that will be dispatched by the
     * controller, and has the following values :
     * <dl>
     * <dt>what</dt>
     * <dd>{@link FragmentController#MSG_DISPATCH_MESSAGE} => Dispatches the
     * message in the controller</dd>
     * <dt>arg1</dt>
     * <dd>The control ID assigned to the ControlledFragment that owns this
     * callback</dd>
     * <dt>arg2</dt>
     * <dd>The {@code what} passed as a parameter that needs to be dispatched</dd>
     * <dt>obj</dt>
     * <dd>{@link Message} that will get dispatched (the {@code what} will be
     * added to it)</dd>
     * </dl>
     * obj is a message instance that will eventually contain what, arg1 passed
     * as parameters of this method.<br>
     * 
     * @param what
     *            message ID to get dispatched by the controller
     * @param arg1
     *            first message int argument
     * @return {@code true} if message could be sent, {@code false} otherwise
     */
    @Override
    public final boolean sendToController(int what, int arg1) {
        return sendToController(what, arg1, 0, null);
    }

    /**
     * Sends a message to the controller via its supplied {@link Messenger}
     * <p>
     * A {@link ControlledFragment} can only communicate with the
     * {@link FragmentController}.
     * </p>
     * The sent message wraps the actual message that will be dispatched by the
     * controller, and has the following values :
     * <dl>
     * <dt>what</dt>
     * <dd>{@link FragmentController#MSG_DISPATCH_MESSAGE} => Dispatches the
     * message in the controller</dd>
     * <dt>arg1</dt>
     * <dd>The control ID assigned to the ControlledFragment that owns this
     * callback</dd>
     * <dt>arg2</dt>
     * <dd>The {@code what} passed as a parameter that needs to be dispatched</dd>
     * <dt>obj</dt>
     * <dd>{@link Message} that will get dispatched (the {@code what} will be
     * added to it)</dd>
     * </dl>
     * obj is a message instance that will eventually contain what, obj passed
     * as parameters of this method.<br>
     * 
     * @param what
     *            message ID to get dispatched by the controller
     * @param obj
     *            Object argument
     * @return {@code true} if message could be sent, {@code false} otherwise
     */
    @Override
    public final boolean sendToController(int what, Object obj) {
        return sendToController(what, 0, 0, obj);
    }

    /**
     * Sends a message to the controller via its supplied {@link Messenger}
     * <p>
     * A {@link ControlledFragment} can only communicate with the
     * {@link FragmentController}.
     * </p>
     * The sent message wraps the actual message that will be dispatched by the
     * controller, and has the following values :
     * <dl>
     * <dt>what</dt>
     * <dd>{@link FragmentController#MSG_DISPATCH_MESSAGE} => Dispatches the
     * message in the controller</dd>
     * <dt>arg1</dt>
     * <dd>The control ID assigned to the ControlledFragment that owns this
     * callback</dd>
     * <dt>arg2</dt>
     * <dd>The {@code what} passed as a parameter that needs to be dispatched</dd>
     * <dt>obj</dt>
     * <dd>{@link Message} that will get dispatched (the {@code what} will be
     * added to it)</dd>
     * </dl>
     * obj is a message instance that will eventually contain what, arg1, arg2
     * passed as parameters of this method.<br>
     * 
     * @param what
     *            message ID to get dispatched by the controller
     * @param arg1
     *            first message int argument
     * @param arg2
     *            second message int argment
     * @return {@code true} if message could be sent, {@code false} otherwise
     */
    @Override
    public final boolean sendToController(int what, int arg1, int arg2) {
        return sendToController(what, arg1, arg2, null);
    }

    /**
     * Sends a message to the controller via its supplied {@link Messenger}
     * <p>
     * A {@link ControlledFragment} can only communicate with the
     * {@link FragmentController}.
     * </p>
     * The sent message wraps the actual message that will be dispatched by the
     * controller, and has the following values :
     * <dl>
     * <dt>what</dt>
     * <dd>{@link FragmentController#MSG_DISPATCH_MESSAGE} => Dispatches the
     * message in the controller</dd>
     * <dt>arg1</dt>
     * <dd>The control ID assigned to the ControlledFragment that owns this
     * callback</dd>
     * <dt>arg2</dt>
     * <dd>The {@code what} passed as a parameter that needs to be dispatched</dd>
     * <dt>obj</dt>
     * <dd>{@link Message} that will get dispatched (the {@code what} will be
     * added to it)</dd>
     * </dl>
     * obj is a message instance that will eventually contain what, arg1, obj
     * passed as parameters of this method.<br>
     * 
     * @param what
     *            message ID to get dispatched by the controller
     * @param arg1
     *            first message int argument
     * @param obj
     *            Object argument
     * @return {@code true} if message could be sent, {@code false} otherwise
     */
    @Override
    public final boolean sendToController(int what, int arg1, Object obj) {
        return sendToController(what, arg1, 0, obj);
    }

    /**
     * Sends a message to the controller via its supplied {@link Messenger}
     * <p>
     * A {@link ControlledFragment} can only communicate with the
     * {@link FragmentController}.
     * </p>
     * The sent message wraps the actual message that will be dispatched by the
     * controller, and has the following values :
     * <dl>
     * <dt>what</dt>
     * <dd>{@link FragmentController#MSG_DISPATCH_MESSAGE} => Dispatches the
     * message in the controller</dd>
     * <dt>arg1</dt>
     * <dd>The control ID assigned to the ControlledFragment that owns this
     * callback</dd>
     * <dt>arg2</dt>
     * <dd>The {@code what} passed as a parameter that needs to be dispatched</dd>
     * <dt>obj</dt>
     * <dd>{@link Message} that will get dispatched (the {@code what} will be
     * added to it)</dd>
     * </dl>
     * obj is a message instance that will eventually contain what, arg1, arg2,
     * obj passed as parameters of this method.<br>
     * 
     * @param what
     *            message ID to get dispatched by the controller
     * @param arg1
     *            first message int argument
     * @param arg2
     *            second message int argment
     * @param obj
     *            Object argument
     * @return {@code true} if message could be sent, {@code false} otherwise
     */
    @Override
    public final boolean sendToController(int what, int arg1, int arg2, Object obj) {
        if (null != mControllerMessenger) {
            Message m = Message.obtain();
            m.what = FragmentController.MSG_DISPATCH_MESSAGE;
            m.arg1 = getControlId();
            m.arg2 = what;
            m.obj = FragmentController.CallbackMessage.obtain(arg1, arg2, obj);
            Bundle b = new Bundle();
            b.putString("controllableName", mControllableName);
            m.setData(b);
            try {
                mControllerMessenger.send(m);
            } catch (RemoteException e) {
                e.printStackTrace();
                return false;
            }
        }
        return true;
    }

    /**
     * Sends a message to itself
     * 
     * @param what
     *            message ID to send to itself
     * @return {@code true} if message could be sent, {@code false} otherwise
     */
    @Override
    public final boolean sendToSelf(int what) {
        return sendToSelf(what, 0, 0, null);
    }

    /**
     * Sends a message to itself
     * 
     * @param what
     *            message ID to send to itself
     * @param arg1
     *            first message int argument
     * @return {@code true} if message could be sent, {@code false} otherwise
     */
    @Override
    public final boolean sendToSelf(int what, int arg1) {
        return sendToSelf(what, arg1, 0, null);
    }

    /**
     * Sends a message to itself
     * 
     * @param what
     *            message ID to send to itself
     * @param obj
     *            Object argument
     * @return {@code true} if message could be sent, {@code false} otherwise
     */
    @Override
    public final boolean sendToSelf(int what, Object obj) {
        return sendToSelf(what, 0, 0, obj);
    }

    /**
     * Sends a message to itself
     * 
     * @param what
     *            message ID to send to itself
     * @param arg1
     *            first message int argument
     * @param arg2
     *            second message int argment
     * @return {@code true} if message could be sent, {@code false} otherwise
     */
    @Override
    public final boolean sendToSelf(int what, int arg1, int arg2) {
        return sendToSelf(what, arg1, arg2, null);
    }

    /**
     * Sends a message to itself
     * 
     * @param what
     *            message ID to send to itself
     * @param arg1
     *            first message int argument
     * @param obj
     *            Object argument
     * @return {@code true} if message could be sent, {@code false} otherwise
     */
    @Override
    public final boolean sendToSelf(int what, int arg1, Object obj) {
        return sendToSelf(what, arg1, 0, obj);
    }

    /**
     * Sends a message to itself
     * 
     * @param what
     *            message ID to send to itself
     * @param arg1
     *            first message int argument
     * @param arg2
     *            second message int argment
     * @param obj
     *            Object argument
     * @return {@code true} if message could be sent, {@code false} otherwise
     */
    @Override
    public final boolean sendToSelf(int what, int arg1, int arg2, Object obj) {
        Message m = Message.obtain();
        m.what = what;
        m.arg1 = arg1;
        m.arg2 = arg2;
        m.obj = obj;
        return getHandler().sendMessage(m);
    }

    /**
     * Sends the specified Runnable to run on the UI thread
     * 
     * @param runnable
     *            the task to run on the UI thread
     * @return itself for chaining
     */
    @Override
    public final ControlledFragment sendToUi(Runnable runnable) {
        if (null != getActivity()) {
            getActivity().runOnUiThread(runnable);
        }
        return this;
    }

    /**
     * Returns the control Id associated with this fragment
     * 
     * @return the control ID
     */
    @Override
    public final int getControlId() {
        return mControlId;
    }

    /**
     * Returns the {@link Handler} associated with current fragment
     * 
     * @return the handler
     */
    @Override
    public final Handler getHandler() {
        return mHandler;
    }

    /**
     * Returns a name for this fragment that can be used ot identify the thread
     * the handler is running within
     * 
     * @return the fragment name
     */
    protected String doGetFragmentName() {
        return getClass().getSimpleName();
    }

    /**
     * Returns the {@code Class} to be used to handle messages targeting this
     * fragment
     * 
     * @return The {@code Class}, subclass of {@link ControlledFragmentCallback}
     */
    protected Class<? extends ControlledFragmentCallback> doGetCallbackClass() {
        return null;
    }

    private Messenger mControllerMessenger = null;
    private int mControlId = -1;
    private String mControllableName = null;

    private Handler mHandler;
    /** null if runsInOwnThread is false during creation */
    private HandlerThread _handlerThread;
}