/* * Copyright 2016 Square Inc. * * 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 flow; import android.app.Activity; import android.app.Application; import android.app.Fragment; import android.content.Intent; import android.os.Bundle; import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import java.util.ArrayList; import java.util.Iterator; import static flow.Preconditions.checkArgument; import static flow.Preconditions.checkNotNull; /** * Pay no attention to this class. It's only public because it has to be. */ public final class InternalLifecycleIntegration extends Fragment { static final String TAG = "flow-lifecycle-integration"; static final String PERSISTENCE_KEY = InternalLifecycleIntegration.class.getSimpleName() + "_state"; static final String INTENT_KEY = InternalLifecycleIntegration.class.getSimpleName() + "_history"; static @Nullable InternalLifecycleIntegration find(Activity activity) { return (InternalLifecycleIntegration) activity.getFragmentManager().findFragmentByTag(TAG); } static @NonNull InternalLifecycleIntegration require(Activity activity) { Fragment fragmentByTag = find(activity); if (fragmentByTag == null) { throw new IllegalStateException("Flow services are not yet available. Do not make this call " + "before receiving Activity#onPause()."); } return (InternalLifecycleIntegration) fragmentByTag; } static void install(final Application app, final Activity activity, @Nullable final KeyParceler parceler, final History defaultHistory, final Dispatcher dispatcher, final KeyManager keyManager) { app.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { @Override public void onActivityCreated(Activity a, Bundle savedInstanceState) { if (a == activity) { InternalLifecycleIntegration fragment = find(activity); boolean newFragment = fragment == null; if (newFragment) { fragment = new InternalLifecycleIntegration(); } if (fragment.keyManager == null) { fragment.defaultHistory = defaultHistory; fragment.parceler = parceler; fragment.keyManager = keyManager; } // We always replace the dispatcher because it frequently references the Activity. fragment.dispatcher = dispatcher; fragment.intent = a.getIntent(); if (newFragment) { activity.getFragmentManager() // .beginTransaction() // .add(fragment, TAG) // .commit(); } app.unregisterActivityLifecycleCallbacks(this); } } @Override public void onActivityStarted(Activity activity) { } @Override public void onActivityResumed(Activity activity) { } @Override public void onActivityPaused(Activity activity) { } @Override public void onActivityStopped(Activity activity) { } @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) { } @Override public void onActivityDestroyed(Activity a) { } }); } Flow flow; KeyManager keyManager; @Nullable KeyParceler parceler; History defaultHistory; Dispatcher dispatcher; Intent intent; private boolean dispatcherSet; public InternalLifecycleIntegration() { super(); setRetainInstance(true); } static void addHistoryToIntent(Intent intent, History history, KeyParceler parceler) { Bundle bundle = new Bundle(); ArrayList<Parcelable> parcelables = new ArrayList<>(history.size()); final Iterator<Object> keys = history.reverseIterator(); while (keys.hasNext()) { Object key = keys.next(); parcelables.add(State.empty(key).toBundle(parceler)); } bundle.putParcelableArrayList(PERSISTENCE_KEY, parcelables); intent.putExtra(INTENT_KEY, bundle); } void onNewIntent(Intent intent) { if (intent.hasExtra(INTENT_KEY)) { checkNotNull(parceler, "Intent has a Flow history extra, but Flow was not installed with a KeyParceler"); History.Builder builder = History.emptyBuilder(); load((Bundle) intent.getParcelableExtra(INTENT_KEY), parceler, builder, keyManager); flow.setHistory(builder.build(), Direction.REPLACE); } } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); if (flow == null) { History savedHistory = null; if (savedInstanceState != null && savedInstanceState.containsKey(INTENT_KEY)) { checkNotNull(parceler, "no KeyParceler installed"); History.Builder builder = History.emptyBuilder(); Bundle bundle = savedInstanceState.getParcelable(INTENT_KEY); load(bundle, parceler, builder, keyManager); savedHistory = builder.build(); } History history = selectHistory(intent, savedHistory, defaultHistory, parceler, keyManager); flow = new Flow(keyManager, history); flow.setDispatcher(dispatcher, false); } else { flow.setDispatcher(dispatcher, true); } dispatcherSet = true; } @Override public void onResume() { super.onResume(); if (!dispatcherSet) { flow.setDispatcher(dispatcher, true); dispatcherSet = true; } } @Override public void onPause() { flow.removeDispatcher(dispatcher); dispatcherSet = false; super.onPause(); } @Override public void onDestroy() { keyManager.tearDown(flow.getHistory().top()); super.onDestroy(); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); checkArgument(outState != null, "outState may not be null"); if (parceler == null) { return; } Bundle bundle = new Bundle(); save(bundle, parceler, flow.getFilteredHistory(), keyManager); if (!bundle.isEmpty()) { outState.putParcelable(INTENT_KEY, bundle); } } private static History selectHistory(Intent intent, History saved, History defaultHistory, @Nullable KeyParceler parceler, KeyManager keyManager) { if (saved != null) { return saved; } if (intent != null && intent.hasExtra(INTENT_KEY)) { checkNotNull(parceler, "Intent has a Flow history extra, but Flow was not installed with a KeyParceler"); History.Builder history = History.emptyBuilder(); load(intent.<Bundle>getParcelableExtra(INTENT_KEY), parceler, history, keyManager); return history.build(); } return defaultHistory; } private static void save(Bundle bundle, KeyParceler parceler, History history, KeyManager keyManager) { ArrayList<Parcelable> parcelables = new ArrayList<>(history.size()); final Iterator<Object> keys = history.reverseIterator(); while (keys.hasNext()) { Object key = keys.next(); if (!key.getClass().isAnnotationPresent(NotPersistent.class)) { parcelables.add(keyManager.getState(key).toBundle(parceler)); } } bundle.putParcelableArrayList(PERSISTENCE_KEY, parcelables); } private static void load(Bundle bundle, KeyParceler parceler, History.Builder builder, KeyManager keyManager) { if (!bundle.containsKey(PERSISTENCE_KEY)) return; ArrayList<Parcelable> stateBundles = bundle.getParcelableArrayList(PERSISTENCE_KEY); //noinspection ConstantConditions for (Parcelable stateBundle : stateBundles) { State state = State.fromBundle((Bundle) stateBundle, parceler); builder.push(state.getKey()); if (!keyManager.hasState(state.getKey())) { keyManager.addState(state); } } } }