/*
* 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.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.SparseArray;
import android.view.View;
import java.util.LinkedHashMap;
import java.util.Map;
import static flow.Preconditions.checkNotNull;
public class State {
private static final String VIEW_STATE_IDS = "VIEW_STATE_IDS";
private static final String BUNDLE = "BUNDLE";
private static final String VIEW_STATE_PREFIX = "VIEW_STATE_";
private static final String KEY = "KEY";
/** Creates a State instance that has no state and is effectively immutable. */
@NonNull public static State empty(@NonNull final Object key) {
return new EmptyState(key);
}
@NonNull static State fromBundle(@NonNull Bundle savedState, @NonNull KeyParceler parceler) {
Object key = parceler.toKey(savedState.getParcelable(KEY));
State state = new State(key);
int[] viewIds = checkNotNull(savedState.getIntArray(VIEW_STATE_IDS), "Null view state ids?");
for (int viewId : viewIds) {
SparseArray<Parcelable> viewState =
savedState.getSparseParcelableArray(VIEW_STATE_PREFIX + viewId);
if (viewState != null) {
state.viewStateById.put(viewId, viewState);
}
}
state.bundle = savedState.getBundle(BUNDLE);
return state;
}
private final Object key;
@Nullable private Bundle bundle;
@NonNull private Map<Integer, SparseArray<Parcelable>> viewStateById = new LinkedHashMap<>();
State(Object key) {
// No external instances.
this.key = key;
}
@NonNull public final <T> T getKey() {
@SuppressWarnings("unchecked") final T state = (T) key;
return state;
}
/**
* Save view hierarchy state so it can be restored later from {@link #restore(View)}. The view
* must have a non-zero id.
*/
public void save(@NonNull View view) {
int viewId = view.getId();
Preconditions.checkArgument(viewId != 0,
"Cannot save state for View with no id " + view.getClass().getSimpleName());
SparseArray<Parcelable> state = new SparseArray<>();
view.saveHierarchyState(state);
viewStateById.put(viewId, state);
}
public void restore(@NonNull View view) {
SparseArray<Parcelable> viewState = viewStateById.get(view.getId());
if (viewState != null) {
view.restoreHierarchyState(viewState);
}
}
public void setBundle(@Nullable Bundle bundle) {
this.bundle = bundle;
}
@Nullable public Bundle getBundle() {
return bundle;
}
Bundle toBundle(KeyParceler parceler) {
Bundle outState = new Bundle();
outState.putParcelable(KEY, parceler.toParcelable(getKey()));
int[] viewIds = new int[viewStateById.size()];
int c = 0;
for (Map.Entry<Integer, SparseArray<Parcelable>> entry : viewStateById.entrySet()) {
Integer viewId = entry.getKey();
viewIds[c++] = viewId;
SparseArray<Parcelable> viewState = entry.getValue();
if (viewState.size() > 0) {
outState.putSparseParcelableArray(VIEW_STATE_PREFIX + viewId, viewState);
}
}
outState.putIntArray(VIEW_STATE_IDS, viewIds);
if (bundle != null && !bundle.isEmpty()) {
outState.putBundle(BUNDLE, bundle);
}
return outState;
}
@Override public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
State state = (State) o;
return (getKey().equals(state.getKey()));
}
@Override public int hashCode() {
return getKey().hashCode();
}
@Override public String toString() {
return getKey().toString();
}
private static final class EmptyState extends State {
EmptyState(Object flowState) {
super(flowState);
}
@Override public void save(@NonNull View view) {
}
@Override public void restore(@NonNull View view) {
}
@Override public void setBundle(Bundle bundle) {
}
@Nullable @Override public Bundle getBundle() {
return null;
}
}
}