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
17package android.databinding;
18
19import android.annotation.TargetApi;
20import android.content.res.ColorStateList;
21import android.databinding.CallbackRegistry.NotifierCallback;
22import android.graphics.drawable.Drawable;
23import android.os.Build.VERSION;
24import android.os.Build.VERSION_CODES;
25import android.os.Handler;
26import android.os.Looper;
27import android.text.TextUtils;
28import android.util.LongSparseArray;
29import android.util.SparseArray;
30import android.util.SparseBooleanArray;
31import android.util.SparseIntArray;
32import android.util.SparseLongArray;
33import android.view.Choreographer;
34import android.view.LayoutInflater;
35import android.view.View;
36import android.view.View.OnAttachStateChangeListener;
37import android.view.ViewGroup;
38
39import com.android.databinding.library.R;
40
41import java.lang.ref.WeakReference;
42import java.util.List;
43import 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 */
51public 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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
363    void forceExecuteBindings() {
364        executeBindings();
365    }
366
367    /**
368     * @hide
369     */
370    protected abstract void executeBindings();
371
372    /**
373     * Invalidates all binding expressions and requests a new rebind to refresh UI.
374     */
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     */
382    public abstract boolean hasPendingBindings();
383
384    /**
385     * Removes binding listeners to expression variables.
386     */
387    public void unbind() {
388        for (WeakListener weakListener : mLocalFieldObservers) {
389            if (weakListener != null) {
390                weakListener.unregister();
391            }
392        }
393    }
394
395    @Override
396    protected void finalize() throws Throwable {
397        unbind();
398    }
399
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     */
420    public View getRoot() {
421        return mRoot;
422    }
423
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     */
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     */
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     */
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
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     */
492    protected boolean updateRegistration(int localFieldId, Observable observable) {
493        return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
494    }
495
496    /**
497     * @hide
498     */
499    protected boolean updateRegistration(int localFieldId, ObservableList observable) {
500        return updateRegistration(localFieldId, observable, CREATE_LIST_LISTENER);
501    }
502
503    /**
504     * @hide
505     */
506    protected boolean updateRegistration(int localFieldId, ObservableMap observable) {
507        return updateRegistration(localFieldId, observable, CREATE_MAP_LISTENER);
508    }
509
510    /**
511     * @hide
512     */
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     */
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     */
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     */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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 */
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)
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)
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 */
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 */
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 */
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 */
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 */
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 */
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)
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)
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 */
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 */
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 */
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     */
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
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
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
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
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     */
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> {
1071        WeakListener<T> getListener();
1072        void addListener(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
1081        public WeakListener(ViewDataBinding binder, int localFieldId,
1082                ObservableReference<T> observable) {
1083            super(binder);
1084            mLocalFieldId = localFieldId;
1085            mObservable = observable;
1086        }
1087
1088        public void setTarget(T object) {
1089            unregister();
1090            mTarget = object;
1091            if (mTarget != null) {
1092                mObservable.addListener(mTarget);
1093            }
1094        }
1095
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
1106        public T getTarget() {
1107            return mTarget;
1108        }
1109
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
1123        public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
1124            mListener = new WeakListener<Observable>(binder, localFieldId, this);
1125        }
1126
1127        @Override
1128        public WeakListener<Observable> getListener() {
1129            return mListener;
1130        }
1131
1132        @Override
1133        public void addListener(Observable target) {
1134            target.addOnPropertyChangedCallback(this);
1135        }
1136
1137        @Override
1138        public void removeListener(Observable target) {
1139            target.removeOnPropertyChangedCallback(this);
1140        }
1141
1142        @Override
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
1160        public WeakListListener(ViewDataBinding binder, int localFieldId) {
1161            mListener = new WeakListener<ObservableList>(binder, localFieldId, this);
1162        }
1163
1164        @Override
1165        public WeakListener<ObservableList> getListener() {
1166            return mListener;
1167        }
1168
1169        @Override
1170        public void addListener(ObservableList target) {
1171            target.addOnListChangedCallback(this);
1172        }
1173
1174        @Override
1175        public void removeListener(ObservableList target) {
1176            target.removeOnListChangedCallback(this);
1177        }
1178
1179        @Override
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
1193        public void onItemRangeChanged(ObservableList sender, int positionStart, int itemCount) {
1194            onChanged(sender);
1195        }
1196
1197        @Override
1198        public void onItemRangeInserted(ObservableList sender, int positionStart, int itemCount) {
1199            onChanged(sender);
1200        }
1201
1202        @Override
1203        public void onItemRangeMoved(ObservableList sender, int fromPosition, int toPosition,
1204                int itemCount) {
1205            onChanged(sender);
1206        }
1207
1208        @Override
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
1218        public WeakMapListener(ViewDataBinding binder, int localFieldId) {
1219            mListener = new WeakListener<ObservableMap>(binder, localFieldId, this);
1220        }
1221
1222        @Override
1223        public WeakListener<ObservableMap> getListener() {
1224            return mListener;
1225        }
1226
1227        @Override
1228        public void addListener(ObservableMap target) {
1229            target.addOnMapChangedCallback(this);
1230        }
1231
1232        @Override
1233        public void removeListener(ObservableMap target) {
1234            target.removeOnMapChangedCallback(this);
1235        }
1236
1237        @Override
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 {
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
1262        public IncludedLayouts(int bindingCount) {
1263            layouts = new String[bindingCount][];
1264            indexes = new int[bindingCount][];
1265            layoutIds = new int[bindingCount][];
1266        }
1267
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
1285        public PropertyChangedInverseListener(int propertyId) {
1286            mPropertyId = propertyId;
1287        }
1288
1289        @Override
1290        public void onPropertyChanged(Observable sender, int propertyId) {
1291            if (propertyId == mPropertyId || propertyId == 0) {
1292                onChange();
1293            }
1294        }
1295    }
1296}
1297