com.apptentive.android.sdk.module.engagement.interaction.fragment.ApptentiveBaseFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.apptentive.android.sdk.module.engagement.interaction.fragment.ApptentiveBaseFragment.java

Source

/*
 * Copyright (c) 2016, Apptentive, Inc. All Rights Reserved.
 * Please refer to the LICENSE file for the terms and conditions
 * under which redistribution and use of this file is permitted.
 */

package com.apptentive.android.sdk.module.engagement.interaction.fragment;

import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;

import android.content.res.Resources;
import android.graphics.PorterDuff;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentHostCallback;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.widget.FrameLayout;

import com.apptentive.android.sdk.ApptentiveInternal;
import com.apptentive.android.sdk.ApptentiveLog;
import com.apptentive.android.sdk.ApptentiveViewExitType;
import com.apptentive.android.sdk.R;
import com.apptentive.android.sdk.module.engagement.EngagementModule;
import com.apptentive.android.sdk.module.engagement.interaction.InteractionManager;
import com.apptentive.android.sdk.module.engagement.interaction.model.Interaction;
import com.apptentive.android.sdk.util.Constants;
import com.apptentive.android.sdk.util.StringUtils;
import com.apptentive.android.sdk.util.Util;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public abstract class ApptentiveBaseFragment<T extends Interaction> extends DialogFragment
        implements InteractionManager.InteractionUpdateListener {

    protected static final String EVENT_NAME_LAUNCH = "launch";
    private static final String HAS_LAUNCHED = "has_launched";

    private final String fragmentName = getClass().getSimpleName();

    /* Nested Fragment with ChildFragmentManager lost state in rev20/rev21 of Android support library
     * The following are needed to work around this issue
     */
    private FragmentManager retainedChildFragmentManager;
    private Class fragmentImplClass;
    private Field hostField;

    private int toolbarLayoutId = 0;
    private Toolbar toolbar = null;
    private List fragmentMenuItems = null;
    private boolean isChangingConfigurations;
    private boolean bShownAsModal;

    protected T interaction;
    protected boolean hasLaunched;
    protected String sectionTitle;

    private OnFragmentTransitionListener onTransitionListener;

    public interface OnFragmentTransitionListener {
        void onFragmentTransition(ApptentiveBaseFragment currentFragment);
    }

    {

        // Prepare the reflections to manage hidden fields
        try {
            fragmentImplClass = Class.forName("android.support.v4.app.FragmentManagerImpl");
            hostField = fragmentImplClass.getDeclaredField("mHost");
            hostField.setAccessible(true);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("FragmentManagerImpl is renamed due to the "
                    + "change of Android SDK, this workaround doesn't work any more. " + "See the issue at "
                    + "https://code.google.com/p/android/issues/detail?id=74222", e);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException("FragmentManagerImpl.mHost is found due to the "
                    + "change of Android SDK, this workaround doesn't work any more. " + "See the issue at "
                    + "https://code.google.com/p/android/issues/detail?id=74222", e);
        }
    }

    //use the retained childFragmentManager after the rotation.
    public FragmentManager getRetainedChildFragmentManager() {
        if (retainedChildFragmentManager == null) {
            retainedChildFragmentManager = getChildFragmentManager();
        }
        return retainedChildFragmentManager;
    }

    public String getFragmentName() {
        return fragmentName;
    }

    public boolean isChangingConfigurations() {
        // When true, it indicates the fragment was destroyed then re-created again (as result of change in orientation )
        return isChangingConfigurations;
    }

    public Context getContext() {
        Context context = super.getContext();
        return context != null ? context : ApptentiveInternal.getInstance().getApplicationContext();
    }

    public void setOnTransitionListener(OnFragmentTransitionListener onTransitionListener) {
        this.onTransitionListener = onTransitionListener;
    }

    public void transit() {
        if (onTransitionListener != null) {
            onTransitionListener.onFragmentTransition(this);
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putBoolean(HAS_LAUNCHED, hasLaunched);
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (retainedChildFragmentManager != null) {
            //Use the retained child fragment manager after rotation
            try {
                //Set the retained ChildFragmentManager to the field
                Field field = Fragment.class.getDeclaredField("mChildFragmentManager");
                field.setAccessible(true);
                field.set(this, retainedChildFragmentManager);

                updateHosts(getFragmentManager(), (FragmentHostCallback) hostField.get(getFragmentManager()));
            } catch (Exception e) {
                ApptentiveLog.w(e.getMessage(), e);
            }
        } else {
            //If the child fragment manager has not been retained yet
            retainedChildFragmentManager = getChildFragmentManager();
        }

    }

    private void updateHosts(FragmentManager fragmentManager, FragmentHostCallback currentHost)
            throws IllegalAccessException {
        if (fragmentManager != null) {
            replaceFragmentManagerHost(fragmentManager, currentHost);
        }

        //replace host(activity) of fragments already added
        List<Fragment> fragments = fragmentManager.getFragments();
        if (fragments != null) {
            for (Fragment fragment : fragments) {
                if (fragment != null) {
                    try {
                        //Copy the mHost(Activity) to retainedChildFragmentManager
                        Field mHostField = Fragment.class.getDeclaredField("mHost");
                        mHostField.setAccessible(true);
                        mHostField.set(fragment, currentHost);
                    } catch (Exception e) {
                        ApptentiveLog.w(e.getMessage(), e);
                    }
                    if (fragment.getChildFragmentManager() != null) {
                        updateHosts(fragment.getChildFragmentManager(), currentHost);
                    }
                }
            }
        }
    }

    private void replaceFragmentManagerHost(FragmentManager fragmentManager, FragmentHostCallback currentHost)
            throws IllegalAccessException {
        if (currentHost != null) {
            hostField.set(fragmentManager, currentHost);
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        try {
            Field childFragmentManager = Fragment.class.getDeclaredField("mChildFragmentManager");
            childFragmentManager.setAccessible(true);
            childFragmentManager.set(this, null);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

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

        Bundle bundle = getArguments();

        if (bundle != null) {
            toolbarLayoutId = bundle.getInt(Constants.FragmentConfigKeys.TOOLBAR_ID);
            bShownAsModal = bundle.getBoolean(Constants.FragmentConfigKeys.MODAL, false);
            String interactionString = bundle.getString("interaction");
            if (!TextUtils.isEmpty(interactionString)) {
                interaction = (T) Interaction.Factory.parseInteraction(interactionString);
            }
        }

        if (interaction != null) {
            // Set the title for modal Interactions for TalkBack support here. Fullscreen Interactions will set title in the ViewPager when they page into view.
            if (bShownAsModal) {
                getActivity().setTitle(interaction.getTitle());
            } else {
                sectionTitle = interaction.getTitle();
            }
        }

        if (toolbarLayoutId != 0 && getMenuResourceId() != 0) {
            setHasOptionsMenu(true);
        }

        if (savedInstanceState != null) {
            hasLaunched = savedInstanceState.getBoolean(HAS_LAUNCHED);
        }
        if (!hasLaunched) {
            hasLaunched = true;
            sendLaunchEvent(getActivity());
        }
    }

    /**
     * Override this in cases where the behavior needs to be different. For instance, the About screen doesn't have an interaction.
     *
     * @param activity The launching Activity
     */
    protected void sendLaunchEvent(Activity activity) {
        if (interaction != null) {
            EngagementModule.engageInternal(activity, interaction, EVENT_NAME_LAUNCH);
        }
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        if (toolbarLayoutId != 0) {
            toolbar = (Toolbar) getActivity().findViewById(toolbarLayoutId);
            if (getMenuResourceId() != 0 && toolbar != null) {
                Menu parentMenu = toolbar.getMenu();
                ArrayList parentMenuItems = new ArrayList();
                // Before creating fragment, host activity may already has menu items on toolbar
                for (int fragmentMenu = 0; fragmentMenu < parentMenu.size(); ++fragmentMenu) {
                    parentMenuItems.add(Integer.valueOf(parentMenu.getItem(fragmentMenu).getItemId()));
                }
                // Add to toolbar menu items and menu listeners created this fragment
                toolbar.inflateMenu(getMenuResourceId());
                attachFragmentMenuListeners(toolbar.getMenu());

                //
                Menu combinedMenu = toolbar.getMenu();
                fragmentMenuItems = new ArrayList();

                int colorControlNormal = Util.getThemeColor(
                        ApptentiveInternal.getInstance().getApptentiveToolbarTheme(), R.attr.colorControlNormal);
                for (int i = 0; i < combinedMenu.size(); ++i) {
                    int menuItemId = combinedMenu.getItem(i).getItemId();
                    // fragmentMenuItems contains new menu items added by this fragment
                    if (!parentMenuItems.contains(Integer.valueOf(menuItemId))) {
                        fragmentMenuItems.add(Integer.valueOf(menuItemId));
                        Drawable drawable = combinedMenu.getItem(i).getIcon();
                        if (drawable != null) {
                            drawable.mutate();
                            drawable.setColorFilter(colorControlNormal, PorterDuff.Mode.SRC_ATOP);
                        }
                    }
                }
            }
        }

    }

    @Override
    public void onResume() {
        ApptentiveInternal.getInstance().addInteractionUpdateListener(this);
        super.onResume();
    }

    @Override
    public void onPause() {
        ApptentiveInternal.getInstance().removeInteractionUpdateListener(this);
        super.onPause();
    }

    @Override
    public void onInteractionUpdated(boolean successful) {
    }

    @Override
    public void onStop() {
        super.onStop();
        if (Build.VERSION.SDK_INT >= 11 && getActivity() != null) {
            isChangingConfigurations = getActivity().isChangingConfigurations();
        }

    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        if (toolbar != null && fragmentMenuItems != null) {
            Menu toolbarMenu = toolbar.getMenu();
            Iterator it = fragmentMenuItems.iterator();
            // Remove menu items added by this fragment
            while (it.hasNext()) {
                Integer menuItem = (Integer) it.next();

                toolbarMenu.removeItem(menuItem.intValue());
            }
        }

    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        inflater.inflate(getMenuResourceId(), menu);
        // Make menu icon color same as toolbar up arrow. Both use ?colorControlNormal
        // By default colorControlNormal has same value as textColorPrimary defined in toolbar theme overlay
        int colorControlNormal = Util.getThemeColor(ApptentiveInternal.getInstance().getApptentiveToolbarTheme(),
                R.attr.colorControlNormal);
        for (int i = 0; i < menu.size(); i++) {
            Drawable drawable = menu.getItem(i).getIcon();
            if (drawable != null) {
                drawable.mutate();
                drawable.setColorFilter(colorControlNormal, PorterDuff.Mode.SRC_ATOP);
            }
        }
        attachFragmentMenuListeners(menu);
        super.onCreateOptionsMenu(menu, inflater);
    }

    protected Bundle getBundle() {
        Bundle bundle = new Bundle();

        if (toolbarLayoutId != 0) {
            bundle.putInt(Constants.FragmentConfigKeys.TOOLBAR_ID, toolbarLayoutId);
        }

        return bundle;
    }

    protected int getMenuResourceId() {
        return 0;
    }

    // If return 0, toolbar use up arrow as default navigation icon
    public int getToolbarNavigationIconResourceId(Resources.Theme activityTheme) {
        return 0;
    }

    protected void attachFragmentMenuListeners(Menu menu) {
    }

    protected void updateMenuVisibility() {

    }

    public Activity getActivity(Fragment fragment) {
        if (fragment == null) {
            return null;
        } else {
            while (fragment.getParentFragment() != null) {
                fragment = fragment.getParentFragment();
            }

            return fragment.getActivity();
        }
    }

    public boolean isShownAsModalDialog() {
        Bundle bundle = getArguments();

        if (bundle != null) {
            return bundle.getBoolean(Constants.FragmentConfigKeys.MODAL, false);
        }
        return false;
    }

    public String getTitle() {
        return sectionTitle;
    }

    public void showToolbarElevation(boolean visible) {
        if (Build.VERSION.SDK_INT >= 21) {
            showToolbarElevationLollipop(visible);
        } else {
            showToolbarElevationPreLollipop(visible);
        }

    }

    /**
     * Delegates the hardware or software back button press to the Interaction Fragment.
     * @return
     */
    public boolean onFragmentExit(ApptentiveViewExitType exitType) {
        List fragments = getRetainedChildFragmentManager().getFragments();

        if (fragments != null) {
            Iterator it = fragments.iterator();

            while (it.hasNext()) {
                Fragment fragment = (Fragment) it.next();

                if (fragment != null && fragment.isVisible()) {
                    FragmentManager childFragmentManager = fragment.getChildFragmentManager();

                    if (childFragmentManager.getBackStackEntryCount() > 0) {
                        childFragmentManager.popBackStack();
                        return true;
                    }
                }
            }
        }

        return false;
    }

    protected String exitTypeToDataJson(ApptentiveViewExitType exitType) {
        return exitType.isShouldAddToEngage() ? StringUtils.asJson("cause", exitType.getName()) : null;
    }

    @TargetApi(21)
    private void showToolbarElevationLollipop(boolean visible) {
        if (!isAdded()) {
            return;
        }
        if (toolbar != null) {
            if (visible) {
                toolbar.setElevation(Util.dipsToPixels(getContext(), 4.0F));
            } else {
                toolbar.setElevation(0.0F);
            }
        } else {
            ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();

            if (actionBar != null) {
                if (visible) {
                    actionBar.setElevation(Util.dipsToPixels(getContext(), 4.0F));
                } else {
                    actionBar.setElevation(0.0F);
                }
            }
        }

    }

    private void showToolbarElevationPreLollipop(boolean visible) {
        if (!isAdded()) {
            return;
        }

        FrameLayout pager = (FrameLayout) getActivity().findViewById(R.id.apptentive_vp_container);

        if (pager != null) {
            if (visible) {
                Drawable shadow = ContextCompat.getDrawable(getContext(),
                        R.drawable.apptentive_actionbar_compat_shadow);
                pager.setForeground(shadow);
            } else {
                pager.setForeground(new ColorDrawable(0));
            }
        }

    }

    public static void replaceFragment(FragmentManager fragmentManager, int fragmentContainerId, Fragment fragment,
            String tag, String parentFragment, boolean showAnimation, boolean executePendingTransactions) {
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        if (showAnimation) {
            fragmentTransaction.setCustomAnimations(R.anim.apptentive_slide_right_in,
                    R.anim.apptentive_slide_left_out, R.anim.apptentive_slide_left_in,
                    R.anim.apptentive_slide_right_out);
        }

        fragmentTransaction.replace(fragmentContainerId, fragment, tag);
        if (!TextUtils.isEmpty(parentFragment)) {
            fragmentTransaction.addToBackStack(parentFragment);
        }

        fragmentTransaction.commit();
        if (executePendingTransactions) {
            fragmentManager.executePendingTransactions();
        }

    }

    public static void popBackStack(FragmentManager fragmentManager, String backStackName) {
        fragmentManager.popBackStack(backStackName, 1);
    }

    public static void popBackStackImmediate(FragmentManager fragmentManager, String backStackName) {
        fragmentManager.popBackStackImmediate(backStackName, 1);
    }

    public static void removeFragment(FragmentManager fragmentManager, Fragment fragment) {
        fragmentManager.beginTransaction().remove(fragment).commit();
    }

}