com.twofortyfouram.locale.sdk.host.ui.fragment.AbstractSupportPluginEditFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.twofortyfouram.locale.sdk.host.ui.fragment.AbstractSupportPluginEditFragment.java

Source

/*
 * android-plugin-host-sdk-for-locale https://github.com/twofortyfouram/android-plugin-host-sdk-for-locale
 * Copyright 2015 two forty four a.m. LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the
 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.twofortyfouram.locale.sdk.host.ui.fragment;

import android.annotation.TargetApi;
import android.app.ActionBar;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;

import com.twofortyfouram.locale.sdk.host.internal.PluginEditDelegate;
import com.twofortyfouram.locale.sdk.host.model.Plugin;
import com.twofortyfouram.locale.sdk.host.model.PluginErrorEdit;
import com.twofortyfouram.locale.sdk.host.model.PluginInstanceData;
import com.twofortyfouram.log.Lumberjack;
import com.twofortyfouram.spackle.AndroidSdkVersion;
import com.twofortyfouram.spackle.bundle.BundleComparer;

import net.jcip.annotations.NotThreadSafe;

import java.util.EnumSet;

import static com.twofortyfouram.assertion.Assertions.assertNotNull;

/**
 * UI-less Fragment to handle communication between the host UI and the plug-in
 * UI. This Fragment will handle launching the plug-in's edit screen, process
 * the Activity result, and deliver a callback to subclasses via
 * {@link IPluginEditFragment#handleSave(Plugin, PluginInstanceData)}.
 * <p>
 * After a plug-in is edited, this Fragment will remove itself automatically.
 * <p>
 * When starting this fragment, the argument
 * {@link IPluginEditFragment#ARG_EXTRA_PARCELABLE_CURRENT_PLUGIN} is required. The optional
 * argument {@link IPluginEditFragment#ARG_EXTRA_PARCELABLE_PREVIOUS_PLUGIN_INSTANCE_DATA} is used
 * if an old plug-in
 * instance is being edited.
 */
@NotThreadSafe
public abstract class AbstractSupportPluginEditFragment extends Fragment implements IPluginEditFragment {

    /**
     * Type: {@code boolean}.
     */
    @NonNull
    private static final String STATE_BOOLEAN_IS_SAVED_INSTANCE = AbstractSupportPluginEditFragment.class.getName()
            + ".state.BOOLEAN_IS_SAVED_INSTANCE";
    //$NON-NLS

    /**
     * Request code for launching a plug-in "edit" Activity.  Subclasses cannot use this request
     * code.
     */
    protected static final int REQUEST_CODE_EDIT_PLUGIN = 1;

    /**
     * The plug-in that is currently being edited.
     */
    @Nullable
    private Plugin mPlugin = null;

    /**
     * Optional previous instance data of the plug-in being edited.
     */
    @Nullable
    private PluginInstanceData mPreviousPluginInstanceData = null;

    /**
     * Builds a new instance of the Fragment's required and optional arguments.
     *
     * @param plugin                     The plug-in to edit with this Fragment.
     * @param previousPluginInstanceData The optional previously saved plug-in
     *                                   instance.
     * @return Args necessary for starting {@link AbstractPluginEditFragment}.
     */
    @NonNull
    public static Bundle newArgs(@NonNull final Plugin plugin,
            @Nullable final PluginInstanceData previousPluginInstanceData) {
        assertNotNull(plugin, "plugin"); //$NON-NLS-1$

        return PluginEditDelegate.newArgs(plugin, previousPluginInstanceData);
    }

    @Override
    public void onAttach(final Context context) {
        super.onAttach(context);

        final Bundle args = getArguments();
        if (null == args) {
            throw new IllegalArgumentException("arguments are missing"); //$NON-NLS-1$
        }

        mPlugin = PluginEditDelegate.getCurrentPlugin(args);
        mPreviousPluginInstanceData = PluginEditDelegate.getPreviousPluginInstanceData(args);
    }

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

        if (null == savedInstanceState) {
            String breadcrumb = null;
            if (AndroidSdkVersion.isAtLeastSdk(Build.VERSION_CODES.HONEYCOMB)) {
                breadcrumb = getBreadcrumbHoneycomb();
            } else {
                final CharSequence title = getActivity().getTitle();
                if (null != title) {
                    breadcrumb = title.toString();
                }
            }

            final Intent i = PluginEditDelegate.getPluginStartIntent(mPlugin, mPreviousPluginInstanceData,
                    breadcrumb);
            try {
                startActivityForResult(i, REQUEST_CODE_EDIT_PLUGIN);
            } catch (final ActivityNotFoundException e) {
                handleErrorsInternal(mPlugin, EnumSet.of(PluginErrorEdit.ACTIVITY_NOT_FOUND_EXCEPTION));
            } catch (final SecurityException e) {
                handleErrorsInternal(mPlugin, EnumSet.of(PluginErrorEdit.SECURITY_EXCEPTION));
            }
        }
    }

    @Override
    public void onSaveInstanceState(@NonNull final Bundle outState) {
        super.onSaveInstanceState(outState);

        // It appears necessary to put *something* in the bundle so that savedInstanceState will be
        // non-null in onCreate() if the fragment is re-created.
        outState.putBoolean(STATE_BOOLEAN_IS_SAVED_INSTANCE, true); //$NON-NLS
    }

    @Nullable
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private String getBreadcrumbHoneycomb() {
        String result = null;

        final ActionBar ab = getActivity().getActionBar();
        if (null != ab) {
            final CharSequence subtitle = ab.getSubtitle();
            if (null != subtitle) {
                result = subtitle.toString();
            }
        }

        return result;
    }

    /**
     * Processes sub-activity results for plug-ins being edited {@inheritDoc}
     */
    @Override
    public final void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent intent) {

        if (REQUEST_CODE_EDIT_PLUGIN == requestCode) {
            switch (resultCode) {
            case Activity.RESULT_OK: {
                Lumberjack.always("Received result code RESULT_OK"); //$NON-NLS-1$

                final EnumSet<PluginErrorEdit> errors = PluginEditDelegate.isIntentValid(intent, mPlugin);
                if (errors.isEmpty()) {
                    final Bundle newBundle = intent
                            .getBundleExtra(com.twofortyfouram.locale.api.Intent.EXTRA_BUNDLE);
                    final String newBlurb = intent
                            .getStringExtra(com.twofortyfouram.locale.api.Intent.EXTRA_STRING_BLURB);

                    Bundle previousBundle = null;
                    String previousBlurb = null;
                    if (null != mPreviousPluginInstanceData) {
                        try {
                            previousBundle = com.twofortyfouram.locale.sdk.host.internal.BundleSerializer
                                    .deserializeFromByteArray(mPreviousPluginInstanceData.getSerializedBundle());
                        } catch (final ClassNotFoundException e) {
                            Lumberjack.e("Error deserializing previous bundle %s", //$NON-NLS-1$
                                    e);
                        }
                        previousBlurb = mPreviousPluginInstanceData.getBlurb();
                    }

                    handleSaveInternal(newBundle, newBlurb, previousBundle, previousBlurb);
                } else {
                    handleErrorsInternal(mPlugin, errors);
                }

                break;
            }
            case Activity.RESULT_CANCELED: {
                Lumberjack.always("Received result code RESULT_CANCELED"); //$NON-NLS-1$
                handleCancelInternal(mPlugin);
                break;
            }
            default: {
                Lumberjack.always("ERROR: Received illegal result code %d", //$NON-NLS-1$
                        resultCode);
                handleErrorsInternal(mPlugin, EnumSet.of(PluginErrorEdit.UNKNOWN_ACTIVITY_RESULT_CODE));
                /*
                 * Although this shouldn't happen, don't throw an exception
                 * because bad 3rd party apps could give bad result codes
                 */

            }
            }
        } else {
            super.onActivityResult(requestCode, resultCode, intent);
        }
    }

    /**
     * Called when a plug-in is canceled.
     *
     * @param plugin The plug-in.
     */
    private void handleCancelInternal(@NonNull final Plugin plugin) {
        assertNotNull(plugin, "plugin"); //$NON-NLS-1$
        Lumberjack.v("Plug-in canceled"); //$NON-NLS-1$
        handleCancel(plugin);

        removeSelf();
    }

    /**
     * Called when a plug-in has an error.
     *
     * @param plugin The plug-in
     * @param errors The errors that occurred.
     */
    private void handleErrorsInternal(@NonNull final Plugin plugin,
            @NonNull final EnumSet<PluginErrorEdit> errors) {
        Lumberjack.v("Encountered errors: %s", errors); //$NON-NLS-1$

        handleErrors(plugin, errors);

        removeSelf();
    }

    /**
     * Internal implementation to handle when a plug-in is ready to be saved.
     *
     * @param newBundle The new plug-in Bundle.
     * @param newBlurb  The new plug-in blurb.
     * @param oldBundle The old plug-in Bundle. This parameter may be
     *                  {@code null} if this is a new plug-in instance, and not
     *                  {@code null} if this is an old plug-in instance that is being
     *                  edited.
     * @param oldBlurb  The old plug-in blurb. This parameter may be {@code null}
     *                  if this is a new plug-in instance, and not {@code null} if
     *                  this is an old plug-in instance that is being edited.
     */
    private void handleSaveInternal(@NonNull final Bundle newBundle, @NonNull final String newBlurb,
            @Nullable final Bundle oldBundle, @Nullable final String oldBlurb) {

        if (!newBlurb.equals(oldBlurb) || !BundleComparer.areBundlesEqual(newBundle, oldBundle)) {
            final EnumSet<PluginErrorEdit> errors = EnumSet.noneOf(PluginErrorEdit.class);
            final byte[] serializedBundle = PluginEditDelegate.serializeBundle(newBundle, errors);
            if (null != serializedBundle) {
                handleSave(mPlugin, new PluginInstanceData(mPlugin.getType(), mPlugin.getRegistryName(),
                        serializedBundle, newBlurb));
                removeSelf();
            } else {
                handleErrorsInternal(mPlugin, errors);
            }
        } else {
            removeSelf();
        }
    }

    private void removeSelf() {
        getFragmentManager().beginTransaction().remove(this).commit();
    }
}