1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tv.settings.widget;
18 
19 import android.os.Bundle;
20 import android.os.Parcelable;
21 import android.util.SparseArray;
22 import android.view.View;
23 
24 /**
25  * Maintains a bundle of states for a group of views. Each view must have a unique id to identify
26  * it. There are four different strategies {@link #SAVE_NO_CHILD} {@link #SAVE_VISIBLE_CHILD}
27  * {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}.
28  * <p>
29  * Why we "invent" another set of strategies beyond the default android view hierarchy saving
30  * mechanism? Because android strategy for saving view states has two limitations: all indirect
31  * descendant views must have a unique view id or their content will be messed together; no way of
32  * defining saving removed views. Those two limitations are critical to AdapterView: AdapterView
33  * will inevitably have two descendant views with same view id, we also need save the views when
34  * they are scrolled out of viewport and removed.
35  * <p>
36  * The class is currently used within {@link ScrollAdapterView}, but it might be used by other
37  * ViewGroup.
38  */
39 public abstract class ViewsStateBundle {
40 
41     /** dont save states of any child views */
42     public static final int SAVE_NO_CHILD = 0;
43     /** only save visible child views, the states are lost when they are gone */
44     public static final int SAVE_VISIBLE_CHILD = 1;
45     /** save visible views plus save removed child views states up to {@link #getLimitNumber()} */
46     public static final int SAVE_LIMITED_CHILD = 2;
47     /**
48      * save visible views plus save removed child views without any limitation. This might cause out
49      * of memory, only use it when you are dealing with limited data
50      */
51     public static final int SAVE_ALL_CHILD = 3;
52 
53     public static final int SAVE_LIMITED_CHILD_DEFAULT_VALUE = 100;
54 
55     private int savePolicy;
56     private int limitNumber;
57 
58     private final Bundle childStates;
59 
ViewsStateBundle(int policy, int limit)60     public ViewsStateBundle(int policy, int limit) {
61         savePolicy = policy;
62         limitNumber = limit;
63         childStates = new Bundle();
64     }
65 
clear()66     public void clear() {
67         childStates.clear();
68     }
69 
70     /**
71      * @return the saved views states
72      */
getChildStates()73     public final Bundle getChildStates() {
74         return childStates;
75     }
76 
77     /**
78      * @return the savePolicy, see {@link #SAVE_NO_CHILD} {@link #SAVE_VISIBLE_CHILD}
79      *         {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}
80      */
getSavePolicy()81     public final int getSavePolicy() {
82         return savePolicy;
83     }
84 
85     /**
86      * @return the limitNumber, only works when {@link #getSavePolicy()} is
87      *         {@link #SAVE_LIMITED_CHILD}
88      */
getLimitNumber()89     public final int getLimitNumber() {
90         return limitNumber;
91     }
92 
93     /**
94      * @see ViewsStateBundle#getSavePolicy()
95      */
setSavePolicy(int savePolicy)96     public final void setSavePolicy(int savePolicy) {
97         this.savePolicy = savePolicy;
98     }
99 
100     /**
101      * @see ViewsStateBundle#getLimitNumber()
102      */
setLimitNumber(int limitNumber)103     public final void setLimitNumber(int limitNumber) {
104         this.limitNumber = limitNumber;
105     }
106 
107     /**
108      * Load view from states, it's none operation if the there is no state associated with the id.
109      *
110      * @param view view where loads into
111      * @param id unique id for the view within this ViewsStateBundle
112      */
loadView(View view, int id)113     public final void loadView(View view, int id) {
114         String key = getSaveStatesKey(id);
115         SparseArray<Parcelable> container = childStates.getSparseParcelableArray(key);
116         if (container != null) {
117             view.restoreHierarchyState(container);
118         }
119     }
120 
121     /**
122      * Save views regardless what's the current policy is.
123      *
124      * @param view view to save
125      * @param id unique id for the view within this ViewsStateBundle
126      */
saveViewUnchecked(View view, int id)127     protected final void saveViewUnchecked(View view, int id) {
128         String key = getSaveStatesKey(id);
129         SparseArray<Parcelable> container = new SparseArray<Parcelable>();
130         view.saveHierarchyState(container);
131         childStates.putSparseParcelableArray(key, container);
132     }
133 
134     /**
135      * The visible view is saved when policy is not {@link #SAVE_NO_CHILD}.
136      *
137      * @param view
138      * @param id
139      */
saveVisibleView(View view, int id)140     public final void saveVisibleView(View view, int id) {
141         if (savePolicy != SAVE_NO_CHILD) {
142             saveViewUnchecked(view, id);
143         }
144     }
145 
146     /**
147      * Save all visible views
148      */
saveVisibleViews()149     public final void saveVisibleViews() {
150         if (savePolicy != SAVE_NO_CHILD) {
151             saveVisibleViewsUnchecked();
152         }
153     }
154 
155     /**
156      * Save list of visible views without checking policy. The method is to be implemented by
157      * subclass, client should use {@link #saveVisibleViews()}.
158      */
saveVisibleViewsUnchecked()159     protected abstract void saveVisibleViewsUnchecked();
160 
161     /**
162      * Save views according to policy.
163      *
164      * @param view view to save
165      * @param id unique id for the view within this ViewsStateBundle
166      */
saveInvisibleView(View view, int id)167     public final void saveInvisibleView(View view, int id) {
168         switch (savePolicy) {
169             case SAVE_LIMITED_CHILD:
170                 if (childStates.size() > limitNumber) {
171                     // TODO prune the Bundle to be under limit
172                 }
173                 // slip through next case section to save view
174             case SAVE_ALL_CHILD:
175                 saveViewUnchecked(view, id);
176                 break;
177             default:
178                 break;
179         }
180     }
181 
getSaveStatesKey(int id)182     static String getSaveStatesKey(int id) {
183         return Integer.toString(id);
184     }
185 }
186