com.albedinsky.android.support.universi.UniversiActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.albedinsky.android.support.universi.UniversiActivity.java

Source

/*
 * =================================================================================================
 *                             Copyright (C) 2014 Martin Albedinsky
 * =================================================================================================
 *         Licensed under the Apache License, Version 2.0 or later (further "License" only).
 * -------------------------------------------------------------------------------------------------
 * You may use this file only in compliance with the License. More details and copy of this License 
 * you may obtain at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 * You can redistribute, modify or publish any part of the code written within this file but as it 
 * is described in the License, the software distributed under the License is distributed on an 
 * "AS IS" BASIS, WITHOUT WARRANTIES or CONDITIONS OF ANY KIND.
 * 
 * See the License for the specific language governing permissions and limitations under the License.
 * =================================================================================================
 */
package com.albedinsky.android.support.universi;

import android.app.ActionBar;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.UiThread;
import android.support.annotation.XmlRes;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.view.Menu;
import android.widget.Toolbar;

import com.albedinsky.android.support.dialog.DialogOptions;
import com.albedinsky.android.support.dialog.manage.DialogController;
import com.albedinsky.android.support.fragment.ActionBarWrapper;
import com.albedinsky.android.support.fragment.BackPressWatcher;
import com.albedinsky.android.support.fragment.annotation.ActionBarOptions;
import com.albedinsky.android.support.fragment.annotation.MenuOptions;
import com.albedinsky.android.support.fragment.manage.FragmentController;
import com.albedinsky.android.support.fragment.util.ActionBarFragmentAnnotationHandler;
import com.albedinsky.android.support.fragment.util.FragmentAnnotations;
import com.albedinsky.android.ui.transition.BaseNavigationalTransition;

/**
 * An {@link FragmentActivity} implementation that provides <b>Universi context</b> features via
 * {@link UniversiActivityDelegate} including other features described below.
 *
 * <h4>1) Data binding</h4>
 * Whether it is used data binding provided by <a href="http://developer.android.com/tools/data-binding/guide.html">Google</a>
 * to bind application logic and layouts or some custom data binding logic, this activity class provides
 * a simple way to manage data binding requests and to perform actual binding. Whether a new data need
 * to be bound to a view hierarchy of a specific instance of UniversiActivity, {@link #requestBindData()}
 * need to be called. <b>This method can be invoked from any thread.</b> If data binding request has
 * been registered, UniversiActivity will invoke {@link #onBindData()} method whenever its view
 * hierarchy is already created or waits until it is created.
 *
 * <h4>1) Permissions</h4>
 * This activity class has support for a new permissions management model introduced in the
 * {@link Build.VERSION_CODES#M Marshmallow} Android version. Permissions related methods like
 * {@link #checkSelfPermission(String)} or {@link #supportRequestPermissions(String[], int)} can be
 * invoked regardless of current Android version.
 *
 * @author Martin Albedinsky
 * @see UniversiCompatActivity
 * @see UniversiFragment
 */
public abstract class UniversiActivity extends FragmentActivity implements UniversiActivityContext {

    /**
     * Interface ===================================================================================
     */

    /**
     * Constants ===================================================================================
     */

    /**
     * Log TAG.
     */
    // private static final String TAG = "UniversiActivity";

    /**
     * Flag indicating whether the output trough log-cat is enabled or not.
     */
    // private static final boolean LOG_ENABLED = true;

    /**
     * Flag indicating whether the debug output trough log-cat is enabled or not.
     */
    // private static final boolean DEBUG_ENABLED = true;

    /**
     * Static members ==============================================================================
     */

    /**
     * Members =====================================================================================
     */

    /**
     * Handler responsible for processing of all annotations of this class and also for handling all
     * annotations related operations for this class.
     */
    final ActionBarFragmentAnnotationHandler mAnnotationHandler;

    /**
     * Delegate that is used to handle requests specific for the Universi context made upon this activity
     * like showing and dismissing of dialogs or showing and hiding of fragments.
     */
    private UniversiActivityDelegate mContextDelegate;

    /**
     * Runnable invoking {@link #requestBindDataInner()} method.
     */
    private Runnable mRequestBindDataInner;

    /**
     * Constructors ================================================================================
     */

    /**
     * Creates a new instance of UniversiActivity. If annotations processing is enabled via {@link UniversiConfig}
     * all annotations supported by this activity class will be processed/obtained here so they can
     * be later used.
     */
    public UniversiActivity() {
        this.mAnnotationHandler = onCreateAnnotationHandler();
    }

    /**
     * Methods =====================================================================================
     */

    /**
     * Invoked to create annotations handler for this instance.
     *
     * @return Annotations handler specific for this class.
     */
    ActionBarFragmentAnnotationHandler onCreateAnnotationHandler() {
        return FragmentAnnotations.obtainActionBarFragmentHandler(getClass());
    }

    /**
     * Returns handler that is responsible for annotations processing of this class and also for
     * handling all annotations related operations for this class.
     *
     * @return Annotations handler specific for this class.
     * @throws IllegalStateException If annotations processing is not enabled for the Fragments library.
     */
    @NonNull
    protected ActionBarFragmentAnnotationHandler getAnnotationHandler() {
        FragmentAnnotations.checkIfEnabledOrThrow();
        return mAnnotationHandler;
    }

    /**
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (mAnnotationHandler != null) {
            final int viewResource = mAnnotationHandler.getContentViewResource(-1);
            if (viewResource != -1) {
                setContentView(viewResource);
            }
        }
    }

    /**
     */
    @Nullable
    @Override
    public <D> Loader<D> startLoader(@IntRange(from = 0) int id, @Nullable Bundle params,
            @NonNull LoaderManager.LoaderCallbacks<D> callbacks) {
        this.ensureContextDelegate();
        return mContextDelegate.startLoader(id, params, callbacks);
    }

    /**
     */
    @Nullable
    @Override
    public <D> Loader<D> initLoader(@IntRange(from = 0) int id, @Nullable Bundle params,
            @NonNull LoaderManager.LoaderCallbacks<D> callbacks) {
        this.ensureContextDelegate();
        return mContextDelegate.initLoader(id, params, callbacks);
    }

    /**
     */
    @Nullable
    @Override
    public <D> Loader<D> restartLoader(@IntRange(from = 0) int id, @Nullable Bundle params,
            @NonNull LoaderManager.LoaderCallbacks<D> callbacks) {
        this.ensureContextDelegate();
        return mContextDelegate.restartLoader(id, params, callbacks);
    }

    /**
     */
    @Override
    public void destroyLoader(@IntRange(from = 0) int id) {
        this.ensureContextDelegate();
        mContextDelegate.destroyLoader(id);
    }

    /**
     */
    @Override
    public void onContentChanged() {
        super.onContentChanged();
        this.ensureContextDelegate();
        mContextDelegate.setViewCreated(true);
        this.configureActionBar(getActionBar());
        onBindViews();
        // Check if there was requested data binding before view creation, if it was, perform binding now.
        if (mContextDelegate.isRequestRegistered(UniversiContextDelegate.REQUEST_BIND_DATA)) {
            mContextDelegate.unregisterRequest(UniversiContextDelegate.REQUEST_BIND_DATA);
            onBindData();
        }
    }

    /**
     */
    @Override
    public void setActionBar(@Nullable Toolbar toolbar) {
        super.setActionBar(toolbar);
        this.configureActionBar(getActionBar());
    }

    /**
     * Called to configure the given <var>actionBar</var> according to the {@link ActionBarOptions @ActionBarOptions}
     * annotation (if presented).
     */
    private void configureActionBar(ActionBar actionBar) {
        if (actionBar == null || mAnnotationHandler == null)
            return;
        mAnnotationHandler.configureActionBar(ActionBarWrapper.wrapActionBar(this, actionBar));
    }

    /**
     * Invoked from {@link #onContentChanged()} to bind all views presented within context of this
     * activity.
     */
    protected void onBindViews() {
    }

    /**
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        if (mAnnotationHandler == null || !mAnnotationHandler.hasOptionsMenu())
            return false;
        final int menuResource = mAnnotationHandler.getOptionsMenuResource(-1);
        if (menuResource != -1) {
            if (mAnnotationHandler.shouldClearOptionsMenu()) {
                menu.clear();
            }
            switch (mAnnotationHandler.getOptionsMenuFlags(0)) {
            case MenuOptions.IGNORE_SUPER:
                getMenuInflater().inflate(menuResource, menu);
                break;
            case MenuOptions.BEFORE_SUPER:
                getMenuInflater().inflate(menuResource, menu);
                super.onCreateOptionsMenu(menu);
                break;
            case MenuOptions.DEFAULT:
                super.onCreateOptionsMenu(menu);
                getMenuInflater().inflate(menuResource, menu);
                break;
            }
            return true;
        }
        return super.onCreateOptionsMenu(menu);
    }

    /**
     */
    @Override
    public void setNavigationalTransition(@Nullable BaseNavigationalTransition transition) {
        this.ensureContextDelegate();
        mContextDelegate.setNavigationalTransition(transition);
    }

    /**
     */
    @Override
    @Nullable
    public BaseNavigationalTransition getNavigationalTransition() {
        this.ensureContextDelegate();
        return mContextDelegate.getNavigationalTransition();
    }

    /**
     */
    @Override
    @NonNull
    public FragmentController getFragmentController() {
        this.ensureContextDelegate();
        return mContextDelegate.getFragmentController();
    }

    /**
     */
    @Override
    public void setFragmentController(@Nullable FragmentController controller) {
        this.ensureContextDelegate();
        mContextDelegate.setFragmentController(controller);
    }

    /**
     */
    @Override
    public void setFragmentFactory(@Nullable FragmentController.FragmentFactory factory) {
        this.ensureContextDelegate();
        mContextDelegate.setFragmentFactory(factory);
    }

    /**
     */
    @Override
    @Nullable
    public FragmentController.FragmentFactory getFragmentFactory() {
        this.ensureContextDelegate();
        return mContextDelegate.getFragmentFactory();
    }

    /**
     */
    @Override
    public void setDialogController(@Nullable DialogController controller) {
        this.ensureContextDelegate();
        mContextDelegate.setDialogController(controller);
    }

    /**
     */
    @Override
    @NonNull
    public DialogController getDialogController() {
        this.ensureContextDelegate();
        return mContextDelegate.getDialogController();
    }

    /**
     */
    @Override
    public void setDialogFactory(@Nullable DialogController.DialogFactory factory) {
        this.ensureContextDelegate();
        mContextDelegate.setDialogFactory(factory);
    }

    /**
     */
    @Override
    @Nullable
    public DialogController.DialogFactory getDialogFactory() {
        this.ensureContextDelegate();
        return mContextDelegate.getDialogFactory();
    }

    /**
     * Requests performing of data binding specific for this activity via {@link #onBindData()}.
     * If this activity has its view hierarchy already created {@link #onBindData()} will be invoked
     * immediately, otherwise will wait until {@link #onContentChanged()} is invoked.
     * <p>
     * <b>This method can be invoked from a background-thread</b>.
     */
    protected void requestBindData() {
        // Check whether this call has been made on the UI thread, if not post on the UI thread the request runnable.
        if (Looper.getMainLooper().equals(Looper.myLooper())) {
            this.requestBindDataInner();
        } else {
            if (mRequestBindDataInner == null) {
                this.mRequestBindDataInner = new Runnable() {
                    @Override
                    public void run() {
                        requestBindDataInner();
                    }
                };
            }
            runOnUiThread(mRequestBindDataInner);
        }
    }

    /**
     * Performs data binding of this activity. Will invoke {@link #onBindData()} if view hierarchy of
     * this activity is already created, otherwise will register a binding request via {@link UniversiContextDelegate#registerRequest(int)}.
     */
    final void requestBindDataInner() {
        this.ensureContextDelegate();
        if (mContextDelegate.isViewCreated()) {
            onBindData();
            return;
        }
        mContextDelegate.registerRequest(UniversiContextDelegate.REQUEST_BIND_DATA);
    }

    /**
     * Invoked due to call to {@link #requestBindData()} to perform data binding specific for this
     * activity instance.
     * <p>
     * <b>This is always invoked on the UI thread.</b>
     */
    @UiThread
    protected void onBindData() {
    }

    /**
     */
    @Override
    protected void onResume() {
        super.onResume();
        this.ensureContextDelegate();
        mContextDelegate.setPaused(false);
    }

    /**
     */
    @Override
    public boolean isActiveNetworkConnected() {
        this.ensureContextDelegate();
        return mContextDelegate.isActiveNetworkConnected();
    }

    /**
     */
    @Override
    public boolean isNetworkConnected(int networkType) {
        this.ensureContextDelegate();
        return mContextDelegate.isNetworkConnected(networkType);
    }

    /**
     */
    @Override
    public int checkSelfPermission(@NonNull String permission) {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? super.checkSelfPermission(permission)
                : PackageManager.PERMISSION_GRANTED;
    }

    /**
     */
    @Override
    public boolean shouldShowRequestPermissionRationale(@NonNull String permission) {
        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
                && super.shouldShowRequestPermissionRationale(permission);
    }

    /**
     * Invokes {@link #requestPermissions(String[], int)} on Android versions above {@link Build.VERSION_CODES#M Marshmallow}
     * (including).
     * <p>
     * Calling this method on Android versions before <b>MARSHMALLOW</b> will be ignored.
     *
     * @param permissions The desired set of permissions to request.
     * @param requestCode Code to identify this request in {@link #onRequestPermissionsResult(int, String[], int[])}.
     */
    public void supportRequestPermissions(@NonNull String[] permissions, int requestCode) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
            requestPermissions(permissions, requestCode);
    }

    /**
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
            @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

    /**
     */
    @Override
    public boolean showDialogWithId(int dialogId) {
        return showDialogWithId(dialogId, null);
    }

    /**
     */
    @Override
    public boolean showDialogWithId(@IntRange(from = 0) int dialogId, @Nullable DialogOptions options) {
        this.ensureContextDelegate();
        return mContextDelegate.showDialogWithId(dialogId, options);
    }

    /**
     */
    @Override
    public boolean dismissDialogWithId(@IntRange(from = 0) int dialogId) {
        this.ensureContextDelegate();
        return mContextDelegate.dismissDialogWithId(dialogId);
    }

    /**
     */
    @Override
    public boolean showXmlDialog(@XmlRes int resId) {
        return showXmlDialog(resId, null);
    }

    /**
     */
    @Override
    public boolean showXmlDialog(@XmlRes int resId, @Nullable DialogOptions options) {
        this.ensureContextDelegate();
        return mContextDelegate.showXmlDialog(resId, options);
    }

    /**
     */
    @Override
    public boolean dismissXmlDialog(@XmlRes int resId) {
        this.ensureContextDelegate();
        return mContextDelegate.dismissXmlDialog(resId);
    }

    /**
     */
    @Override
    public void onBackPressed() {
        if (!onBackPress())
            finishWithNavigationalTransition();
    }

    /**
     * Invoked from {@link #onBackPressed()} to give a chance to this activity instance to consume
     * the back press event before its parent.
     * <p>
     * This implementation first tries to dispatch this event to the current fragment via
     * {@link #dispatchBackPressToCurrentFragment()} and if that method does not consume the event
     * a stack with fragments will be tried to popped via {@link #popFragmentsBackStack()}.
     *
     * @return {@code True} if the back press event has been consumed here, {@code false} otherwise.
     */
    protected boolean onBackPress() {
        this.ensureContextDelegate();
        return !mContextDelegate.isPaused() && (dispatchBackPressToCurrentFragment() || popFragmentsBackStack());
    }

    /**
     * Pops stack with fragments of this activity.
     *
     * @return {@code True} if there was some fragment popped from the stack, {@code false} if there
     * were no fragments to pop from.
     */
    protected boolean popFragmentsBackStack() {
        this.ensureContextDelegate();
        return mContextDelegate.popFragmentsBackStack();
    }

    /**
     * Dispatches back press event to the current fragment of this activity.
     *
     * @return {@code True} if there is presented some fragment that consumed the back press event,
     * {@code false} otherwise.
     * @see #findCurrentFragment()
     * @see BackPressWatcher#dispatchBackPressed()
     */
    protected boolean dispatchBackPressToCurrentFragment() {
        final Fragment fragment = findCurrentFragment();
        return fragment instanceof BackPressWatcher && ((BackPressWatcher) fragment).dispatchBackPressed();
    }

    /**
     * Invoked whenever an event need to be dispatched to the current fragment that is presented
     * within context of this activity.
     *
     * @return Current fragment of this activity (that is at this time visible), or {@code null} if
     * there are no fragments presented within this activity at all.
     */
    @Nullable
    protected Fragment findCurrentFragment() {
        this.ensureContextDelegate();
        return mContextDelegate.findCurrentFragment();
    }

    /**
     */
    @Override
    protected void onPause() {
        super.onPause();
        this.ensureContextDelegate();
        mContextDelegate.setPaused(true);
    }

    /**
     */
    @Override
    public boolean finishWithNavigationalTransition() {
        this.ensureContextDelegate();
        if (mContextDelegate.finishWithNavigationalTransition()) {
            return true;
        }
        supportFinishAfterTransition();
        return false;
    }

    /**
     * Ensures that the context delegate is initialized for this activity.
     */
    private void ensureContextDelegate() {
        if (mContextDelegate == null)
            this.mContextDelegate = UniversiContextDelegate.create(this);
    }

    /**
     * Inner classes ===============================================================================
     */
}