Java tutorial
/* * 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(); } }