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 android.databinding;
18 
19 import android.annotation.TargetApi;
20 import android.content.res.ColorStateList;
21 import android.databinding.CallbackRegistry.NotifierCallback;
22 import android.graphics.drawable.Drawable;
23 import android.os.Build.VERSION;
24 import android.os.Build.VERSION_CODES;
25 import android.os.Handler;
26 import android.os.Looper;
27 import android.text.TextUtils;
28 import android.util.LongSparseArray;
29 import android.util.SparseArray;
30 import android.util.SparseBooleanArray;
31 import android.util.SparseIntArray;
32 import android.util.SparseLongArray;
33 import android.view.Choreographer;
34 import android.view.LayoutInflater;
35 import android.view.View;
36 import android.view.View.OnAttachStateChangeListener;
37 import android.view.ViewGroup;
38 
39 import com.android.databinding.library.R;
40 
41 import java.lang.ref.WeakReference;
42 import java.util.List;
43 import java.util.Map;
44 
45 /**
46  * Base class for generated data binding classes. If possible, the generated binding should
47  * be instantiated using one of its generated static bind or inflate methods. If the specific
48  * binding is unknown, {@link DataBindingUtil#bind(View)} or
49  * {@link DataBindingUtil#inflate(LayoutInflater, int, ViewGroup, boolean)} should be used.
50  */
51 public abstract class ViewDataBinding extends BaseObservable {
52 
53     /**
54      * Instead of directly accessing Build.VERSION.SDK_INT, generated code uses this value so that
55      * we can test API dependent behavior.
56      */
57     static int SDK_INT = VERSION.SDK_INT;
58 
59     private static final int REBIND = 1;
60     private static final int HALTED = 2;
61     private static final int REBOUND = 3;
62 
63     /**
64      * Prefix for android:tag on Views with binding. The root View and include tags will not have
65      * android:tag attributes and will use ids instead.
66      *
67      * @hide
68      */
69     public static final String BINDING_TAG_PREFIX = "binding_";
70 
71     // The length of BINDING_TAG_PREFIX prevents calling length repeatedly.
72     private static final int BINDING_NUMBER_START = BINDING_TAG_PREFIX.length();
73 
74     // ICS (v 14) fixes a leak when using setTag(int, Object)
75     private static final boolean USE_TAG_ID = DataBinderMapper.TARGET_MIN_SDK >= 14;
76 
77     private static final boolean USE_CHOREOGRAPHER = SDK_INT >= 16;
78 
79     /**
80      * Method object extracted out to attach a listener to a bound Observable object.
81      */
82     private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
83         @Override
84         public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
85             return new WeakPropertyListener(viewDataBinding, localFieldId).getListener();
86         }
87     };
88 
89     /**
90      * Method object extracted out to attach a listener to a bound ObservableList object.
91      */
92     private static final CreateWeakListener CREATE_LIST_LISTENER = new CreateWeakListener() {
93         @Override
94         public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
95             return new WeakListListener(viewDataBinding, localFieldId).getListener();
96         }
97     };
98 
99     /**
100      * Method object extracted out to attach a listener to a bound ObservableMap object.
101      */
102     private static final CreateWeakListener CREATE_MAP_LISTENER = new CreateWeakListener() {
103         @Override
104         public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
105             return new WeakMapListener(viewDataBinding, localFieldId).getListener();
106         }
107     };
108 
109     private static final CallbackRegistry.NotifierCallback<OnRebindCallback, ViewDataBinding, Void>
110         REBIND_NOTIFIER = new NotifierCallback<OnRebindCallback, ViewDataBinding, Void>() {
111         @Override
112         public void onNotifyCallback(OnRebindCallback callback, ViewDataBinding sender, int mode,
113                 Void arg2) {
114             switch (mode) {
115                 case REBIND:
116                     if (!callback.onPreBind(sender)) {
117                         sender.mRebindHalted = true;
118                     }
119                     break;
120                 case HALTED:
121                     callback.onCanceled(sender);
122                     break;
123                 case REBOUND:
124                     callback.onBound(sender);
125                     break;
126             }
127         }
128     };
129 
130     private static final OnAttachStateChangeListener ROOT_REATTACHED_LISTENER;
131 
132     static {
133         if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {
134             ROOT_REATTACHED_LISTENER = null;
135         } else {
136             ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() {
137                 @TargetApi(VERSION_CODES.KITKAT)
138                 @Override
139                 public void onViewAttachedToWindow(View v) {
140                     // execute the pending bindings.
141                     final ViewDataBinding binding = getBinding(v);
142                     binding.mRebindRunnable.run();
143                     v.removeOnAttachStateChangeListener(this);
144                 }
145 
146                 @Override
147                 public void onViewDetachedFromWindow(View v) {
148                 }
149             };
150         }
151     }
152 
153     /**
154      * Runnable executed on animation heartbeat to rebind the dirty Views.
155      */
156     private final Runnable mRebindRunnable = new Runnable() {
157         @Override
158         public void run() {
159             synchronized (this) {
160                 mPendingRebind = false;
161             }
162             if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
163                 // Nested so that we don't get a lint warning in IntelliJ
164                 if (!mRoot.isAttachedToWindow()) {
165                     // Don't execute the pending bindings until the View
166                     // is attached again.
167                     mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
168                     mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
169                     return;
170                 }
171             }
172             executePendingBindings();
173         }
174     };
175 
176     /**
177      * Flag indicates that there are pending bindings that need to be reevaluated.
178      */
179     private boolean mPendingRebind = false;
180 
181     /**
182      * Indicates that a onPreBind has stopped the executePendingBindings call.
183      */
184     private boolean mRebindHalted = false;
185 
186     /**
187      * The observed expressions.
188      */
189     private WeakListener[] mLocalFieldObservers;
190 
191     /**
192      * The root View that this Binding is associated with.
193      */
194     private final View mRoot;
195 
196     /**
197      * The collection of OnRebindCallbacks.
198      */
199     private CallbackRegistry<OnRebindCallback, ViewDataBinding, Void> mRebindCallbacks;
200 
201     /**
202      * Flag to prevent reentrant executePendingBinding calls.
203      */
204     private boolean mIsExecutingPendingBindings;
205 
206     // null api < 16
207     private Choreographer mChoreographer;
208 
209     private final Choreographer.FrameCallback mFrameCallback;
210 
211     // null api >= 16
212     private Handler mUIThreadHandler;
213 
214     /**
215      * The DataBindingComponent used by this data binding. This is used for BindingAdapters
216      * that are instance methods to retrieve the class instance that implements the
217      * adapter.
218      *
219      * @hide
220      */
221     protected final DataBindingComponent mBindingComponent;
222 
223     /**
224      * @hide
225      */
ViewDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount)226     protected ViewDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount) {
227         mBindingComponent = bindingComponent;
228         mLocalFieldObservers = new WeakListener[localFieldCount];
229         this.mRoot = root;
230         if (Looper.myLooper() == null) {
231             throw new IllegalStateException("DataBinding must be created in view's UI Thread");
232         }
233         if (USE_CHOREOGRAPHER) {
234             mChoreographer = Choreographer.getInstance();
235             mFrameCallback = new Choreographer.FrameCallback() {
236                 @Override
237                 public void doFrame(long frameTimeNanos) {
238                     mRebindRunnable.run();
239                 }
240             };
241         } else {
242             mFrameCallback = null;
243             mUIThreadHandler = new Handler(Looper.myLooper());
244         }
245     }
246 
247     /**
248      * @hide
249      */
setRootTag(View view)250     protected void setRootTag(View view) {
251         if (USE_TAG_ID) {
252             view.setTag(R.id.dataBinding, this);
253         } else {
254             view.setTag(this);
255         }
256     }
257 
258     /**
259      * @hide
260      */
setRootTag(View[] views)261     protected void setRootTag(View[] views) {
262         if (USE_TAG_ID) {
263             for (View view : views) {
264                 view.setTag(R.id.dataBinding, this);
265             }
266         } else {
267             for (View view : views) {
268                 view.setTag(this);
269             }
270         }
271     }
272 
273     /**
274      * @hide
275      */
getBuildSdkInt()276     public static int getBuildSdkInt() {
277         return SDK_INT;
278     }
279 
280     /**
281      * Called when an observed object changes. Sets the appropriate dirty flag if applicable.
282      * @param localFieldId The index into mLocalFieldObservers that this Object resides in.
283      * @param object The object that has changed.
284      * @param fieldId The BR ID of the field being changed or _all if
285      *                no specific field is being notified.
286      * @return true if this change should cause a change to the UI.
287      * @hide
288      */
onFieldChange(int localFieldId, Object object, int fieldId)289     protected abstract boolean onFieldChange(int localFieldId, Object object, int fieldId);
290 
291     /**
292      * Set a value value in the Binding class.
293      * <p>
294      * Typically, the developer will be able to call the subclass's set method directly. For
295      * example, if there is a variable <code>x</code> in the Binding, a <code>setX</code> method
296      * will be generated. However, there are times when the specific subclass of ViewDataBinding
297      * is unknown, so the generated method cannot be discovered without reflection. The
298      * setVariable call allows the values of variables to be set without reflection.
299      *
300      * @param variableId the BR id of the variable to be set. For example, if the variable is
301      *                   <code>x</code>, then variableId will be <code>BR.x</code>.
302      * @param value The new value of the variable to be set.
303      * @return <code>true</code> if the variable is declared or used in the binding or
304      * <code>false</code> otherwise.
305      */
setVariable(int variableId, Object value)306     public abstract boolean setVariable(int variableId, Object value);
307 
308     /**
309      * Add a listener to be called when reevaluating dirty fields. This also allows automatic
310      * updates to be halted, but does not stop explicit calls to {@link #executePendingBindings()}.
311      *
312      * @param listener The listener to add.
313      */
addOnRebindCallback(OnRebindCallback listener)314     public void addOnRebindCallback(OnRebindCallback listener) {
315         if (mRebindCallbacks == null) {
316             mRebindCallbacks = new CallbackRegistry<OnRebindCallback, ViewDataBinding, Void>(REBIND_NOTIFIER);
317         }
318         mRebindCallbacks.add(listener);
319     }
320 
321     /**
322      * Removes a listener that was added in {@link #addOnRebindCallback(OnRebindCallback)}.
323      *
324      * @param listener The listener to remove.
325      */
removeOnRebindCallback(OnRebindCallback listener)326     public void removeOnRebindCallback(OnRebindCallback listener) {
327         if (mRebindCallbacks != null) {
328             mRebindCallbacks.remove(listener);
329         }
330     }
331 
332     /**
333      * Evaluates the pending bindings, updating any Views that have expressions bound to
334      * modified variables. This <b>must</b> be run on the UI thread.
335      */
executePendingBindings()336     public void executePendingBindings() {
337         if (mIsExecutingPendingBindings) {
338             requestRebind();
339             return;
340         }
341         if (!hasPendingBindings()) {
342             return;
343         }
344         mIsExecutingPendingBindings = true;
345         mRebindHalted = false;
346         if (mRebindCallbacks != null) {
347             mRebindCallbacks.notifyCallbacks(this, REBIND, null);
348 
349             // The onRebindListeners will change mPendingHalted
350             if (mRebindHalted) {
351                 mRebindCallbacks.notifyCallbacks(this, HALTED, null);
352             }
353         }
354         if (!mRebindHalted) {
355             executeBindings();
356             if (mRebindCallbacks != null) {
357                 mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
358             }
359         }
360         mIsExecutingPendingBindings = false;
361     }
362 
forceExecuteBindings()363     void forceExecuteBindings() {
364         executeBindings();
365     }
366 
367     /**
368      * @hide
369      */
executeBindings()370     protected abstract void executeBindings();
371 
372     /**
373      * Invalidates all binding expressions and requests a new rebind to refresh UI.
374      */
invalidateAll()375     public abstract void invalidateAll();
376 
377     /**
378      * Returns whether the UI needs to be refresh to represent the current data.
379      *
380      * @return true if any field has changed and the binding should be evaluated.
381      */
hasPendingBindings()382     public abstract boolean hasPendingBindings();
383 
384     /**
385      * Removes binding listeners to expression variables.
386      */
unbind()387     public void unbind() {
388         for (WeakListener weakListener : mLocalFieldObservers) {
389             if (weakListener != null) {
390                 weakListener.unregister();
391             }
392         }
393     }
394 
395     @Override
finalize()396     protected void finalize() throws Throwable {
397         unbind();
398     }
399 
getBinding(View v)400     static ViewDataBinding getBinding(View v) {
401         if (v != null) {
402             if (USE_TAG_ID) {
403                 return (ViewDataBinding) v.getTag(R.id.dataBinding);
404             } else {
405                 final Object tag = v.getTag();
406                 if (tag instanceof ViewDataBinding) {
407                     return (ViewDataBinding) tag;
408                 }
409             }
410         }
411         return null;
412     }
413 
414     /**
415      * Returns the outermost View in the layout file associated with the Binding. If this
416      * binding is for a merge layout file, this will return the first root in the merge tag.
417      *
418      * @return the outermost View in the layout file associated with the Binding.
419      */
getRoot()420     public View getRoot() {
421         return mRoot;
422     }
423 
handleFieldChange(int mLocalFieldId, Object object, int fieldId)424     private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
425         boolean result = onFieldChange(mLocalFieldId, object, fieldId);
426         if (result) {
427             requestRebind();
428         }
429     }
430 
431     /**
432      * @hide
433      */
unregisterFrom(int localFieldId)434     protected boolean unregisterFrom(int localFieldId) {
435         WeakListener listener = mLocalFieldObservers[localFieldId];
436         if (listener != null) {
437             return listener.unregister();
438         }
439         return false;
440     }
441 
442     /**
443      * @hide
444      */
requestRebind()445     protected void requestRebind() {
446         synchronized (this) {
447             if (mPendingRebind) {
448                 return;
449             }
450             mPendingRebind = true;
451         }
452         if (USE_CHOREOGRAPHER) {
453             mChoreographer.postFrameCallback(mFrameCallback);
454         } else {
455             mUIThreadHandler.post(mRebindRunnable);
456         }
457 
458     }
459 
460     /**
461      * @hide
462      */
getObservedField(int localFieldId)463     protected Object getObservedField(int localFieldId) {
464         WeakListener listener = mLocalFieldObservers[localFieldId];
465         if (listener == null) {
466             return null;
467         }
468         return listener.getTarget();
469     }
470 
updateRegistration(int localFieldId, Object observable, CreateWeakListener listenerCreator)471     private boolean updateRegistration(int localFieldId, Object observable,
472             CreateWeakListener listenerCreator) {
473         if (observable == null) {
474             return unregisterFrom(localFieldId);
475         }
476         WeakListener listener = mLocalFieldObservers[localFieldId];
477         if (listener == null) {
478             registerTo(localFieldId, observable, listenerCreator);
479             return true;
480         }
481         if (listener.getTarget() == observable) {
482             return false;//nothing to do, same object
483         }
484         unregisterFrom(localFieldId);
485         registerTo(localFieldId, observable, listenerCreator);
486         return true;
487     }
488 
489     /**
490      * @hide
491      */
updateRegistration(int localFieldId, Observable observable)492     protected boolean updateRegistration(int localFieldId, Observable observable) {
493         return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
494     }
495 
496     /**
497      * @hide
498      */
updateRegistration(int localFieldId, ObservableList observable)499     protected boolean updateRegistration(int localFieldId, ObservableList observable) {
500         return updateRegistration(localFieldId, observable, CREATE_LIST_LISTENER);
501     }
502 
503     /**
504      * @hide
505      */
updateRegistration(int localFieldId, ObservableMap observable)506     protected boolean updateRegistration(int localFieldId, ObservableMap observable) {
507         return updateRegistration(localFieldId, observable, CREATE_MAP_LISTENER);
508     }
509 
510     /**
511      * @hide
512      */
ensureBindingComponentIsNotNull(Class<?> oneExample)513     protected void ensureBindingComponentIsNotNull(Class<?> oneExample) {
514         if (mBindingComponent == null) {
515             String errorMessage = "Required DataBindingComponent is null in class " +
516                     getClass().getSimpleName() + ". A BindingAdapter in " +
517                     oneExample.getCanonicalName() +
518                     " is not static and requires an object to use, retrieved from the " +
519                     "DataBindingComponent. If you don't use an inflation method taking a " +
520                     "DataBindingComponent, use DataBindingUtil.setDefaultComponent or " +
521                     "make all BindingAdapter methods static.";
522             throw new IllegalStateException(errorMessage);
523         }
524     }
525 
526     /**
527      * @hide
528      */
registerTo(int localFieldId, Object observable, CreateWeakListener listenerCreator)529     protected void registerTo(int localFieldId, Object observable,
530             CreateWeakListener listenerCreator) {
531         if (observable == null) {
532             return;
533         }
534         WeakListener listener = mLocalFieldObservers[localFieldId];
535         if (listener == null) {
536             listener = listenerCreator.create(this, localFieldId);
537             mLocalFieldObservers[localFieldId] = listener;
538         }
539         listener.setTarget(observable);
540     }
541 
542     /**
543      * @hide
544      */
bind(DataBindingComponent bindingComponent, View view, int layoutId)545     protected static ViewDataBinding bind(DataBindingComponent bindingComponent, View view,
546             int layoutId) {
547         return DataBindingUtil.bind(bindingComponent, view, layoutId);
548     }
549 
550     /**
551      * Walks the view hierarchy under root and pulls out tagged Views, includes, and views with
552      * IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find
553      * all bound and ID'd views.
554      *
555      * @param bindingComponent The binding component to use with this binding.
556      * @param root The root of the view hierarchy to walk.
557      * @param numBindings The total number of ID'd views, views with expressions, and includes
558      * @param includes The include layout information, indexed by their container's index.
559      * @param viewsWithIds Indexes of views that don't have tags, but have IDs.
560      * @return An array of size numBindings containing all Views in the hierarchy that have IDs
561      * (with elements in viewsWithIds), are tagged containing expressions, or the bindings for
562      * included layouts.
563      * @hide
564      */
mapBindings(DataBindingComponent bindingComponent, View root, int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds)565     protected static Object[] mapBindings(DataBindingComponent bindingComponent, View root,
566             int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {
567         Object[] bindings = new Object[numBindings];
568         mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true);
569         return bindings;
570     }
571 
572     /** @hide */
getColorFromResource(int resourceId)573     protected int getColorFromResource(int resourceId) {
574         if (VERSION.SDK_INT >= VERSION_CODES.M) {
575             return getRoot().getContext().getColor(resourceId);
576         } else {
577             return getRoot().getResources().getColor(resourceId);
578         }
579     }
580 
581     /** @hide */
getColorStateListFromResource(int resourceId)582     protected ColorStateList getColorStateListFromResource(int resourceId) {
583         if (VERSION.SDK_INT >= VERSION_CODES.M) {
584             return getRoot().getContext().getColorStateList(resourceId);
585         } else {
586             return getRoot().getResources().getColorStateList(resourceId);
587         }
588     }
589 
590     /** @hide */
getDrawableFromResource(int resourceId)591     protected Drawable getDrawableFromResource(int resourceId) {
592         if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
593             return getRoot().getContext().getDrawable(resourceId);
594         } else {
595             return getRoot().getResources().getDrawable(resourceId);
596         }
597     }
598 
599     /** @hide */
getFromArray(T[] arr, int index)600     protected static <T> T getFromArray(T[] arr, int index) {
601         if (arr == null || index < 0 || index >= arr.length) {
602             return null;
603         }
604         return arr[index];
605     }
606 
607     /** @hide */
setTo(T[] arr, int index, T value)608     protected static <T> void setTo(T[] arr, int index, T value) {
609         if (arr == null || index < 0 || index >= arr.length) {
610             return;
611         }
612         arr[index] = value;
613     }
614 
615     /** @hide */
getFromArray(boolean[] arr, int index)616     protected static boolean getFromArray(boolean[] arr, int index) {
617         if (arr == null || index < 0 || index >= arr.length) {
618             return false;
619         }
620         return arr[index];
621     }
622 
623     /** @hide */
setTo(boolean[] arr, int index, boolean value)624     protected static void setTo(boolean[] arr, int index, boolean value) {
625         if (arr == null || index < 0 || index >= arr.length) {
626             return;
627         }
628         arr[index] = value;
629     }
630 
631     /** @hide */
getFromArray(byte[] arr, int index)632     protected static byte getFromArray(byte[] arr, int index) {
633         if (arr == null || index < 0 || index >= arr.length) {
634             return 0;
635         }
636         return arr[index];
637     }
638 
639     /** @hide */
setTo(byte[] arr, int index, byte value)640     protected static void setTo(byte[] arr, int index, byte value) {
641         if (arr == null || index < 0 || index >= arr.length) {
642             return;
643         }
644         arr[index] = value;
645     }
646 
647     /** @hide */
getFromArray(short[] arr, int index)648     protected static short getFromArray(short[] arr, int index) {
649         if (arr == null || index < 0 || index >= arr.length) {
650             return 0;
651         }
652         return arr[index];
653     }
654 
655     /** @hide */
setTo(short[] arr, int index, short value)656     protected static void setTo(short[] arr, int index, short value) {
657         if (arr == null || index < 0 || index >= arr.length) {
658             return;
659         }
660         arr[index] = value;
661     }
662 
663     /** @hide */
getFromArray(char[] arr, int index)664     protected static char getFromArray(char[] arr, int index) {
665         if (arr == null || index < 0 || index >= arr.length) {
666             return 0;
667         }
668         return arr[index];
669     }
670 
671     /** @hide */
setTo(char[] arr, int index, char value)672     protected static void setTo(char[] arr, int index, char value) {
673         if (arr == null || index < 0 || index >= arr.length) {
674             return;
675         }
676         arr[index] = value;
677     }
678 
679     /** @hide */
getFromArray(int[] arr, int index)680     protected static int getFromArray(int[] arr, int index) {
681         if (arr == null || index < 0 || index >= arr.length) {
682             return 0;
683         }
684         return arr[index];
685     }
686 
687     /** @hide */
setTo(int[] arr, int index, int value)688     protected static void setTo(int[] arr, int index, int value) {
689         if (arr == null || index < 0 || index >= arr.length) {
690             return;
691         }
692         arr[index] = value;
693     }
694 
695     /** @hide */
getFromArray(long[] arr, int index)696     protected static long getFromArray(long[] arr, int index) {
697         if (arr == null || index < 0 || index >= arr.length) {
698             return 0;
699         }
700         return arr[index];
701     }
702 
703     /** @hide */
setTo(long[] arr, int index, long value)704     protected static void setTo(long[] arr, int index, long value) {
705         if (arr == null || index < 0 || index >= arr.length) {
706             return;
707         }
708         arr[index] = value;
709     }
710 
711     /** @hide */
getFromArray(float[] arr, int index)712     protected static float getFromArray(float[] arr, int index) {
713         if (arr == null || index < 0 || index >= arr.length) {
714             return 0;
715         }
716         return arr[index];
717     }
718 
719     /** @hide */
setTo(float[] arr, int index, float value)720     protected static void setTo(float[] arr, int index, float value) {
721         if (arr == null || index < 0 || index >= arr.length) {
722             return;
723         }
724         arr[index] = value;
725     }
726 
727     /** @hide */
getFromArray(double[] arr, int index)728     protected static double getFromArray(double[] arr, int index) {
729         if (arr == null || index < 0 || index >= arr.length) {
730             return 0;
731         }
732         return arr[index];
733     }
734 
735     /** @hide */
setTo(double[] arr, int index, double value)736     protected static void setTo(double[] arr, int index, double value) {
737         if (arr == null || index < 0 || index >= arr.length) {
738             return;
739         }
740         arr[index] = value;
741     }
742 
743     /** @hide */
getFromList(List<T> list, int index)744     protected static <T> T getFromList(List<T> list, int index) {
745         if (list == null || index < 0 || index >= list.size()) {
746             return null;
747         }
748         return list.get(index);
749     }
750 
751     /** @hide */
setTo(List<T> list, int index, T value)752     protected static <T> void setTo(List<T> list, int index, T value) {
753         if (list == null || index < 0 || index >= list.size()) {
754             return;
755         }
756         list.set(index, value);
757     }
758 
759     /** @hide */
getFromList(SparseArray<T> list, int index)760     protected static <T> T getFromList(SparseArray<T> list, int index) {
761         if (list == null || index < 0) {
762             return null;
763         }
764         return list.get(index);
765     }
766 
767     /** @hide */
setTo(SparseArray<T> list, int index, T value)768     protected static <T> void setTo(SparseArray<T> list, int index, T value) {
769         if (list == null || index < 0 || index >= list.size()) {
770             return;
771         }
772         list.put(index, value);
773     }
774 
775     /** @hide */
776     @TargetApi(VERSION_CODES.JELLY_BEAN)
getFromList(LongSparseArray<T> list, int index)777     protected static <T> T getFromList(LongSparseArray<T> list, int index) {
778         if (list == null || index < 0) {
779             return null;
780         }
781         return list.get(index);
782     }
783 
784     /** @hide */
785     @TargetApi(VERSION_CODES.JELLY_BEAN)
setTo(LongSparseArray<T> list, int index, T value)786     protected static <T> void setTo(LongSparseArray<T> list, int index, T value) {
787         if (list == null || index < 0 || index >= list.size()) {
788             return;
789         }
790         list.put(index, value);
791     }
792 
793     /** @hide */
getFromList(android.support.v4.util.LongSparseArray<T> list, int index)794     protected static <T> T getFromList(android.support.v4.util.LongSparseArray<T> list, int index) {
795         if (list == null || index < 0) {
796             return null;
797         }
798         return list.get(index);
799     }
800 
801     /** @hide */
setTo(android.support.v4.util.LongSparseArray<T> list, int index, T value)802     protected static <T> void setTo(android.support.v4.util.LongSparseArray<T> list, int index,
803             T value) {
804         if (list == null || index < 0 || index >= list.size()) {
805             return;
806         }
807         list.put(index, value);
808     }
809 
810     /** @hide */
getFromList(SparseBooleanArray list, int index)811     protected static boolean getFromList(SparseBooleanArray list, int index) {
812         if (list == null || index < 0) {
813             return false;
814         }
815         return list.get(index);
816     }
817 
818     /** @hide */
setTo(SparseBooleanArray list, int index, boolean value)819     protected static void setTo(SparseBooleanArray list, int index, boolean value) {
820         if (list == null || index < 0 || index >= list.size()) {
821             return;
822         }
823         list.put(index, value);
824     }
825 
826     /** @hide */
getFromList(SparseIntArray list, int index)827     protected static int getFromList(SparseIntArray list, int index) {
828         if (list == null || index < 0) {
829             return 0;
830         }
831         return list.get(index);
832     }
833 
834     /** @hide */
setTo(SparseIntArray list, int index, int value)835     protected static void setTo(SparseIntArray list, int index, int value) {
836         if (list == null || index < 0 || index >= list.size()) {
837             return;
838         }
839         list.put(index, value);
840     }
841 
842     /** @hide */
843     @TargetApi(VERSION_CODES.JELLY_BEAN_MR2)
getFromList(SparseLongArray list, int index)844     protected static long getFromList(SparseLongArray list, int index) {
845         if (list == null || index < 0) {
846             return 0;
847         }
848         return list.get(index);
849     }
850 
851     /** @hide */
852     @TargetApi(VERSION_CODES.JELLY_BEAN_MR2)
setTo(SparseLongArray list, int index, long value)853     protected static void setTo(SparseLongArray list, int index, long value) {
854         if (list == null || index < 0 || index >= list.size()) {
855             return;
856         }
857         list.put(index, value);
858     }
859 
860     /** @hide */
getFrom(Map<K, T> map, K key)861     protected static <K, T> T getFrom(Map<K, T> map, K key) {
862         if (map == null) {
863             return null;
864         }
865         return map.get(key);
866     }
867 
868     /** @hide */
setTo(Map<K, T> map, K key, T value)869     protected static <K, T> void setTo(Map<K, T> map, K key, T value) {
870         if (map == null) {
871             return;
872         }
873         map.put(key, value);
874     }
875 
876     /** @hide */
setBindingInverseListener(ViewDataBinding binder, InverseBindingListener oldListener, PropertyChangedInverseListener listener)877     protected static void setBindingInverseListener(ViewDataBinding binder,
878             InverseBindingListener oldListener, PropertyChangedInverseListener listener) {
879         if (oldListener != listener) {
880             if (oldListener != null) {
881                 binder.removeOnPropertyChangedCallback(
882                         (PropertyChangedInverseListener) oldListener);
883             }
884             if (listener != null) {
885                 binder.addOnPropertyChangedCallback(listener);
886             }
887         }
888     }
889 
890     /**
891      * Walks the view hierarchy under roots and pulls out tagged Views, includes, and views with
892      * IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find
893      * all bound and ID'd views.
894      *
895      * @param bindingComponent The binding component to use with this binding.
896      * @param roots The root Views of the view hierarchy to walk. This is used with merge tags.
897      * @param numBindings The total number of ID'd views, views with expressions, and includes
898      * @param includes The include layout information, indexed by their container's index.
899      * @param viewsWithIds Indexes of views that don't have tags, but have IDs.
900      * @return An array of size numBindings containing all Views in the hierarchy that have IDs
901      * (with elements in viewsWithIds), are tagged containing expressions, or the bindings for
902      * included layouts.
903      * @hide
904      */
mapBindings(DataBindingComponent bindingComponent, View[] roots, int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds)905     protected static Object[] mapBindings(DataBindingComponent bindingComponent, View[] roots,
906             int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {
907         Object[] bindings = new Object[numBindings];
908         for (int i = 0; i < roots.length; i++) {
909             mapBindings(bindingComponent, roots[i], bindings, includes, viewsWithIds, true);
910         }
911         return bindings;
912     }
913 
mapBindings(DataBindingComponent bindingComponent, View view, Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds, boolean isRoot)914     private static void mapBindings(DataBindingComponent bindingComponent, View view,
915             Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
916             boolean isRoot) {
917         final int indexInIncludes;
918         final ViewDataBinding existingBinding = getBinding(view);
919         if (existingBinding != null) {
920             return;
921         }
922         final String tag = (String) view.getTag();
923         boolean isBound = false;
924         if (isRoot && tag != null && tag.startsWith("layout")) {
925             final int underscoreIndex = tag.lastIndexOf('_');
926             if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) {
927                 final int index = parseTagInt(tag, underscoreIndex + 1);
928                 if (bindings[index] == null) {
929                     bindings[index] = view;
930                 }
931                 indexInIncludes = includes == null ? -1 : index;
932                 isBound = true;
933             } else {
934                 indexInIncludes = -1;
935             }
936         } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
937             int tagIndex = parseTagInt(tag, BINDING_NUMBER_START);
938             if (bindings[tagIndex] == null) {
939                 bindings[tagIndex] = view;
940             }
941             isBound = true;
942             indexInIncludes = includes == null ? -1 : tagIndex;
943         } else {
944             // Not a bound view
945             indexInIncludes = -1;
946         }
947         if (!isBound) {
948             final int id = view.getId();
949             if (id > 0) {
950                 int index;
951                 if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0 &&
952                         bindings[index] == null) {
953                     bindings[index] = view;
954                 }
955             }
956         }
957 
958         if (view instanceof  ViewGroup) {
959             final ViewGroup viewGroup = (ViewGroup) view;
960             final int count = viewGroup.getChildCount();
961             int minInclude = 0;
962             for (int i = 0; i < count; i++) {
963                 final View child = viewGroup.getChildAt(i);
964                 boolean isInclude = false;
965                 if (indexInIncludes >= 0) {
966                     String childTag = (String) child.getTag();
967                     if (childTag != null && childTag.endsWith("_0") &&
968                             childTag.startsWith("layout") && childTag.indexOf('/') > 0) {
969                         // This *could* be an include. Test against the expected includes.
970                         int includeIndex = findIncludeIndex(childTag, minInclude,
971                                 includes, indexInIncludes);
972                         if (includeIndex >= 0) {
973                             isInclude = true;
974                             minInclude = includeIndex + 1;
975                             final int index = includes.indexes[indexInIncludes][includeIndex];
976                             final int layoutId = includes.layoutIds[indexInIncludes][includeIndex];
977                             int lastMatchingIndex = findLastMatching(viewGroup, i);
978                             if (lastMatchingIndex == i) {
979                                 bindings[index] = DataBindingUtil.bind(bindingComponent, child,
980                                         layoutId);
981                             } else {
982                                 final int includeCount =  lastMatchingIndex - i + 1;
983                                 final View[] included = new View[includeCount];
984                                 for (int j = 0; j < includeCount; j++) {
985                                     included[j] = viewGroup.getChildAt(i + j);
986                                 }
987                                 bindings[index] = DataBindingUtil.bind(bindingComponent, included,
988                                         layoutId);
989                                 i += includeCount - 1;
990                             }
991                         }
992                     }
993                 }
994                 if (!isInclude) {
995                     mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
996                 }
997             }
998         }
999     }
1000 
findIncludeIndex(String tag, int minInclude, IncludedLayouts included, int includedIndex)1001     private static int findIncludeIndex(String tag, int minInclude,
1002             IncludedLayouts included, int includedIndex) {
1003         final int slashIndex = tag.indexOf('/');
1004         final CharSequence layoutName = tag.subSequence(slashIndex + 1, tag.length() - 2);
1005 
1006         final String[] layouts = included.layouts[includedIndex];
1007         final int length = layouts.length;
1008         for (int i = minInclude; i < length; i++) {
1009             final String layout = layouts[i];
1010             if (TextUtils.equals(layoutName, layout)) {
1011                 return i;
1012             }
1013         }
1014         return -1;
1015     }
1016 
findLastMatching(ViewGroup viewGroup, int firstIncludedIndex)1017     private static int findLastMatching(ViewGroup viewGroup, int firstIncludedIndex) {
1018         final View firstView = viewGroup.getChildAt(firstIncludedIndex);
1019         final String firstViewTag = (String) firstView.getTag();
1020         final String tagBase = firstViewTag.substring(0, firstViewTag.length() - 1); // don't include the "0"
1021         final int tagSequenceIndex = tagBase.length();
1022 
1023         final int count = viewGroup.getChildCount();
1024         int max = firstIncludedIndex;
1025         for (int i = firstIncludedIndex + 1; i < count; i++) {
1026             final View view = viewGroup.getChildAt(i);
1027             final String tag = (String) view.getTag();
1028             if (tag != null && tag.startsWith(tagBase)) {
1029                 if (tag.length() == firstViewTag.length() && tag.charAt(tag.length() - 1) == '0') {
1030                     return max; // Found another instance of the include
1031                 }
1032                 if (isNumeric(tag, tagSequenceIndex)) {
1033                     max = i;
1034                 }
1035             }
1036         }
1037         return max;
1038     }
1039 
isNumeric(String tag, int startIndex)1040     private static boolean isNumeric(String tag, int startIndex) {
1041         int length = tag.length();
1042         if (length == startIndex) {
1043             return false; // no numerals
1044         }
1045         for (int i = startIndex; i < length; i++) {
1046             if (!Character.isDigit(tag.charAt(i))) {
1047                 return false;
1048             }
1049         }
1050         return true;
1051     }
1052 
1053     /**
1054      * Parse the tag without creating a new String object. This is fast and assumes the
1055      * tag is in the correct format.
1056      * @param str The tag string.
1057      * @return The binding tag number parsed from the tag string.
1058      */
parseTagInt(String str, int startIndex)1059     private static int parseTagInt(String str, int startIndex) {
1060         final int end = str.length();
1061         int val = 0;
1062         for (int i = startIndex; i < end; i++) {
1063             val *= 10;
1064             char c = str.charAt(i);
1065             val += (c - '0');
1066         }
1067         return val;
1068     }
1069 
1070     private interface ObservableReference<T> {
getListener()1071         WeakListener<T> getListener();
addListener(T target)1072         void addListener(T target);
removeListener(T target)1073         void removeListener(T target);
1074     }
1075 
1076     private static class WeakListener<T> extends WeakReference<ViewDataBinding> {
1077         private final ObservableReference<T> mObservable;
1078         protected final int mLocalFieldId;
1079         private T mTarget;
1080 
WeakListener(ViewDataBinding binder, int localFieldId, ObservableReference<T> observable)1081         public WeakListener(ViewDataBinding binder, int localFieldId,
1082                 ObservableReference<T> observable) {
1083             super(binder);
1084             mLocalFieldId = localFieldId;
1085             mObservable = observable;
1086         }
1087 
setTarget(T object)1088         public void setTarget(T object) {
1089             unregister();
1090             mTarget = object;
1091             if (mTarget != null) {
1092                 mObservable.addListener(mTarget);
1093             }
1094         }
1095 
unregister()1096         public boolean unregister() {
1097             boolean unregistered = false;
1098             if (mTarget != null) {
1099                 mObservable.removeListener(mTarget);
1100                 unregistered = true;
1101             }
1102             mTarget = null;
1103             return unregistered;
1104         }
1105 
getTarget()1106         public T getTarget() {
1107             return mTarget;
1108         }
1109 
getBinder()1110         protected ViewDataBinding getBinder() {
1111             ViewDataBinding binder = get();
1112             if (binder == null) {
1113                 unregister(); // The binder is dead
1114             }
1115             return binder;
1116         }
1117     }
1118 
1119     private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback
1120             implements ObservableReference<Observable> {
1121         final WeakListener<Observable> mListener;
1122 
WeakPropertyListener(ViewDataBinding binder, int localFieldId)1123         public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
1124             mListener = new WeakListener<Observable>(binder, localFieldId, this);
1125         }
1126 
1127         @Override
getListener()1128         public WeakListener<Observable> getListener() {
1129             return mListener;
1130         }
1131 
1132         @Override
addListener(Observable target)1133         public void addListener(Observable target) {
1134             target.addOnPropertyChangedCallback(this);
1135         }
1136 
1137         @Override
removeListener(Observable target)1138         public void removeListener(Observable target) {
1139             target.removeOnPropertyChangedCallback(this);
1140         }
1141 
1142         @Override
onPropertyChanged(Observable sender, int propertyId)1143         public void onPropertyChanged(Observable sender, int propertyId) {
1144             ViewDataBinding binder = mListener.getBinder();
1145             if (binder == null) {
1146                 return;
1147             }
1148             Observable obj = mListener.getTarget();
1149             if (obj != sender) {
1150                 return; // notification from the wrong object?
1151             }
1152             binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
1153         }
1154     }
1155 
1156     private static class WeakListListener extends ObservableList.OnListChangedCallback
1157             implements ObservableReference<ObservableList> {
1158         final WeakListener<ObservableList> mListener;
1159 
WeakListListener(ViewDataBinding binder, int localFieldId)1160         public WeakListListener(ViewDataBinding binder, int localFieldId) {
1161             mListener = new WeakListener<ObservableList>(binder, localFieldId, this);
1162         }
1163 
1164         @Override
getListener()1165         public WeakListener<ObservableList> getListener() {
1166             return mListener;
1167         }
1168 
1169         @Override
addListener(ObservableList target)1170         public void addListener(ObservableList target) {
1171             target.addOnListChangedCallback(this);
1172         }
1173 
1174         @Override
removeListener(ObservableList target)1175         public void removeListener(ObservableList target) {
1176             target.removeOnListChangedCallback(this);
1177         }
1178 
1179         @Override
onChanged(ObservableList sender)1180         public void onChanged(ObservableList sender) {
1181             ViewDataBinding binder = mListener.getBinder();
1182             if (binder == null) {
1183                 return;
1184             }
1185             ObservableList target = mListener.getTarget();
1186             if (target != sender) {
1187                 return; // We expect notifications only from sender
1188             }
1189             binder.handleFieldChange(mListener.mLocalFieldId, target, 0);
1190         }
1191 
1192         @Override
onItemRangeChanged(ObservableList sender, int positionStart, int itemCount)1193         public void onItemRangeChanged(ObservableList sender, int positionStart, int itemCount) {
1194             onChanged(sender);
1195         }
1196 
1197         @Override
onItemRangeInserted(ObservableList sender, int positionStart, int itemCount)1198         public void onItemRangeInserted(ObservableList sender, int positionStart, int itemCount) {
1199             onChanged(sender);
1200         }
1201 
1202         @Override
onItemRangeMoved(ObservableList sender, int fromPosition, int toPosition, int itemCount)1203         public void onItemRangeMoved(ObservableList sender, int fromPosition, int toPosition,
1204                 int itemCount) {
1205             onChanged(sender);
1206         }
1207 
1208         @Override
onItemRangeRemoved(ObservableList sender, int positionStart, int itemCount)1209         public void onItemRangeRemoved(ObservableList sender, int positionStart, int itemCount) {
1210             onChanged(sender);
1211         }
1212     }
1213 
1214     private static class WeakMapListener extends ObservableMap.OnMapChangedCallback
1215             implements ObservableReference<ObservableMap> {
1216         final WeakListener<ObservableMap> mListener;
1217 
WeakMapListener(ViewDataBinding binder, int localFieldId)1218         public WeakMapListener(ViewDataBinding binder, int localFieldId) {
1219             mListener = new WeakListener<ObservableMap>(binder, localFieldId, this);
1220         }
1221 
1222         @Override
getListener()1223         public WeakListener<ObservableMap> getListener() {
1224             return mListener;
1225         }
1226 
1227         @Override
addListener(ObservableMap target)1228         public void addListener(ObservableMap target) {
1229             target.addOnMapChangedCallback(this);
1230         }
1231 
1232         @Override
removeListener(ObservableMap target)1233         public void removeListener(ObservableMap target) {
1234             target.removeOnMapChangedCallback(this);
1235         }
1236 
1237         @Override
onMapChanged(ObservableMap sender, Object key)1238         public void onMapChanged(ObservableMap sender, Object key) {
1239             ViewDataBinding binder = mListener.getBinder();
1240             if (binder == null || sender != mListener.getTarget()) {
1241                 return;
1242             }
1243             binder.handleFieldChange(mListener.mLocalFieldId, sender, 0);
1244         }
1245     }
1246 
1247     private interface CreateWeakListener {
create(ViewDataBinding viewDataBinding, int localFieldId)1248         WeakListener create(ViewDataBinding viewDataBinding, int localFieldId);
1249     }
1250 
1251     /**
1252      * This class is used by generated subclasses of {@link ViewDataBinding} to track the
1253      * included layouts contained in the bound layout. This class is an implementation
1254      * detail of how binding expressions are mapped to Views after inflation.
1255      * @hide
1256      */
1257     protected static class IncludedLayouts {
1258         public final String[][] layouts;
1259         public final int[][] indexes;
1260         public final int[][] layoutIds;
1261 
IncludedLayouts(int bindingCount)1262         public IncludedLayouts(int bindingCount) {
1263             layouts = new String[bindingCount][];
1264             indexes = new int[bindingCount][];
1265             layoutIds = new int[bindingCount][];
1266         }
1267 
setIncludes(int index, String[] layouts, int[] indexes, int[] layoutIds)1268         public void setIncludes(int index, String[] layouts, int[] indexes, int[] layoutIds) {
1269             this.layouts[index] = layouts;
1270             this.indexes[index] = indexes;
1271             this.layoutIds[index] = layoutIds;
1272         }
1273     }
1274 
1275     /**
1276      * This class is used by generated subclasses of {@link ViewDataBinding} to listen for
1277      * changes on variables of Bindings. This is important for two-way data binding on variables
1278      * in included Bindings.
1279      * @hide
1280      */
1281     protected static abstract class PropertyChangedInverseListener
1282             extends Observable.OnPropertyChangedCallback implements InverseBindingListener {
1283         final int mPropertyId;
1284 
PropertyChangedInverseListener(int propertyId)1285         public PropertyChangedInverseListener(int propertyId) {
1286             mPropertyId = propertyId;
1287         }
1288 
1289         @Override
onPropertyChanged(Observable sender, int propertyId)1290         public void onPropertyChanged(Observable sender, int propertyId) {
1291             if (propertyId == mPropertyId || propertyId == 0) {
1292                 onChange();
1293             }
1294         }
1295     }
1296 }
1297