content: /*
   * Copyright (C) 2016 Airbnb, 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 com.airbnb.rxgroups;
  
  import android.app.Activity;
  import android.app.Fragment;
  import android.os.Bundle;
  import android.os.Parcel;
  import android.os.Parcelable;
  
  import java.util.UUID;
  
  import javax.annotation.Nullable;
  
  import io.reactivex.ObservableTransformer;
  import io.reactivex.Observer;
  
  /**
   * Manges unlocking, locking, and destroying observables based on the lifecycle of an activity or
   * fragment. An Activity or fragment must call:
   * <ul>
   * <li>{@link #onCreate(ObservableManager, Bundle, Object)}</li>
   * <li>{@link #onResume()}</li>
   * <li>{@link #onPause()}</li>
   * <li>{@link #onDestroy(Activity)}</li>
   * <li>{@link #onSaveInstanceState(Bundle)}</li>
   * </ul>
   * in corresponding methods.
   */
  @SuppressWarnings("WeakerAccess")
  public class GroupLifecycleManager {
    private static final String KEY_STATE = "KEY_GROUPLIFECYCLEMANAGER_STATE";
  
    private final ObservableManager observableManager;
    private final ObservableGroup group;
    private boolean hasSavedState;
  
    private GroupLifecycleManager(ObservableManager observableManager, ObservableGroup group) {
      this.observableManager = observableManager;
      this.group = group;
    }
  
    /** Call this method from your Activity or Fragment's onCreate method */
    public static GroupLifecycleManager onCreate(ObservableManager observableManager,
        @Nullable Bundle savedState, @Nullable Object target) {
  
      ObservableGroup group;
      if (savedState != null) {
        State state = savedState.getParcelable(KEY_STATE);
  
        Preconditions.checkState(state != null, "Must call onSaveInstanceState() first");
  
        // First check the instance ID before restoring state. If it's not the same instance,
        // then we have to create a new group since the previous one is already destroyed.
        // Android can sometimes reuse the same instance after saving state and we can't reliably
        // determine when that happens. This is a workaround for that behavior.
        if (state.managerId != observableManager.id()) {
          group = observableManager.newGroup();
        } else {
          group = observableManager.getGroup(state.groupId);
        }
      } else {
        group = observableManager.newGroup();
      }
  
      group.lock();
  
      GroupLifecycleManager manager = new GroupLifecycleManager(observableManager, group);
  
      if (target != null) {
        manager.initializeAutoTaggingAndResubscription(target);
      }
  
      return manager;
    }
  
    /** @return the {@link ObservableGroup} associated to this instance */
    public ObservableGroup group() {
      return group;
    }
  
    /**
     * Calls {@link ObservableGroup#transform(Observer)}  for the group managed by
     * this instance.
     */
    public <T> ObservableTransformer<? super T, T> transform(Observer<? super T> observer) {
      return group.transform(observer);
    }
  
    /**
     * Calls {@link ObservableGroup#transform(Observer, String)}  for the group managed by
     * this instance.
     */
    public <T> ObservableTransformer<? super T, T> transform(Observer<? super T> observer,
            String observableTag) {
      return group.transform(observer, observableTag);
    }
  
    /**
     * Call {@link ObservableGroup#hasObservables(Observer)} for the group managed by
     * this instance.
     */
    public boolean hasObservables(Observer<?> observer) {
      return group.hasObservables(observer);
    }
  
    /**
     * Call {@link ObservableGroup#hasObservable(Observer, String)} for the group managed by
     * this instance.
     */
    public boolean hasObservable(Observer<?> observer, String observableTag) {
      return group.hasObservable(observer, observableTag);
    }
  
    /**
     * Calls
     * {@link ObservableGroup#initializeAutoTaggingAndResubscription(Object)} (Object, Class)}
     * for the group managed by this instance.
     */
    public void initializeAutoTaggingAndResubscription(Object target) {
      Preconditions.checkNotNull(target, "Target cannot be null");
      group.initializeAutoTaggingAndResubscription(target);
    }
  
    /**
     * Calls
     * {@link ObservableGroup#initializeAutoTaggingAndResubscriptionInTargetClassOnly(Object, Class)}
     * for the group managed by this instance.
     */
    public <T> void initializeAutoTaggingAndResubscriptionInTargetClassOnly(T target,
            Class<T> targetClass) {
     group.initializeAutoTaggingAndResubscriptionInTargetClassOnly(target, targetClass);
    }
  
    /**
     * Calls {@link ObservableGroup#cancelAllObservablesForObserver(Observer)} (Observer)}
     * for the group associated with this instance.
     */
    public void cancelAllObservablesForObserver(Observer<?> observer) {
      group.cancelAllObservablesForObserver(observer);
    }
  
    /**
     * Calls {@link ObservableGroup#cancelAndRemove(Observer, String)} for the group
     * associated with this instance.
     */
    public void cancelAndRemove(Observer<?> observer, String observableTag) {
      group.cancelAndRemove(observer, observableTag);
    }
  
    private void onDestroy(boolean isFinishing) {
      if (isFinishing) {
        observableManager.destroy(group);
      } else {
        group().removeNonResubscribableObservers();
        group.dispose();
      }
    }
  
    /** Call this method from your Activity or Fragment's onDestroy method */
    public void onDestroy(@Nullable Activity activity) {
      // We need to track whether the current Activity is finishing or not in order to decide if we
      // should destroy the ObservableGroup. If the Activity is not finishing, then we should not
      // destroy it, since we know that we're probably restoring state at some point and reattaching
      // to the existing ObservableGroup. If saveState() was not called, then it's likely that we're
      // being destroyed and are not ever coming back. However, this isn't perfect, especially
      // when using fragments in a ViewPager. We might want to allow users to explicitly destroy it
      // instead, in order to mitigate this issue.
      // Also in some situations it seems like an Activity can be recreated even though its
      // isFinishing() property is set to true. For this reason, we must also check
      // isChangingConfigurations() to make sure it's safe to destroy the group.
      onDestroy(!hasSavedState
          || (activity != null && activity.isFinishing() && !activity.isChangingConfigurations()));
    }
  
    /** Call this method from your Activity or Fragment's onDestroy method */
    public void onDestroy(Fragment fragment) {
      onDestroy(fragment.getActivity());
    }
  
    /** Call this method from your Activity or Fragment's onResume method */
    public void onResume() {
      hasSavedState = false;
      unlock();
    }
  
    /** Call this method from your Activity or Fragment's onPause method */
    public void onPause() {
      lock();
    }
  
    private void lock() {
      group.lock();
    }
  
    private void unlock() {
      group.unlock();
    }
  
    /** Call this method from your Activity or Fragment's onSaveInstanceState method */
    public void onSaveInstanceState(Bundle outState) {
      hasSavedState = true;
      outState.putParcelable(KEY_STATE, new State(observableManager.id(), group.id()));
    }
  
    static class State implements Parcelable {
      final UUID managerId;
      final long groupId;
  
      State(UUID managerId, long groupId) {
        this.managerId = managerId;
        this.groupId = groupId;
      }
  
      @Override public int describeContents() {
        return 0;
      }
  
      @Override public void writeToParcel(Parcel dest, int flags) {
        dest.writeSerializable(managerId);
        dest.writeLong(groupId);
      }
  
      public static final Parcelable.Creator<State> CREATOR = new Parcelable.Creator<State>() {
        @Override
        public State[] newArray(int size) {
          return new State[size];
        }
  
        @Override
        public State createFromParcel(Parcel source) {
          return new State((UUID) source.readSerializable(), source.readLong());
        }
      };
    }
  }
  