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 static boolean parse(String str, boolean fallback) {
574        if (str == null) {
575            return fallback;
576        }
577        return Boolean.parseBoolean(str);
578    }
579
580    /** @hide */
581    protected static byte parse(String str, byte fallback) {
582        try {
583            return Byte.parseByte(str);
584        } catch (NumberFormatException e) {
585            return fallback;
586        }
587    }
588
589    /** @hide */
590    protected static short parse(String str, short fallback) {
591        try {
592            return Short.parseShort(str);
593        } catch (NumberFormatException e) {
594            return fallback;
595        }
596    }
597
598    /** @hide */
599    protected static int parse(String str, int fallback) {
600        try {
601            return Integer.parseInt(str);
602        } catch (NumberFormatException e) {
603            return fallback;
604        }
605    }
606
607    /** @hide */
608    protected static long parse(String str, long fallback) {
609        try {
610            return Long.parseLong(str);
611        } catch (NumberFormatException e) {
612            return fallback;
613        }
614    }
615
616    /** @hide */
617    protected static float parse(String str, float fallback) {
618        try {
619            return Float.parseFloat(str);
620        } catch (NumberFormatException e) {
621            return fallback;
622        }
623    }
624
625    /** @hide */
626    protected static double parse(String str, double fallback) {
627        try {
628            return Double.parseDouble(str);
629        } catch (NumberFormatException e) {
630            return fallback;
631        }
632    }
633
634    /** @hide */
635    protected static char parse(String str, char fallback) {
636        if (str == null || str.isEmpty()) {
637            return fallback;
638        }
639        return str.charAt(0);
640    }
641
642    /** @hide */
643    protected static int getColorFromResource(View view, int resourceId) {
644        if (VERSION.SDK_INT >= VERSION_CODES.M) {
645            return view.getContext().getColor(resourceId);
646        } else {
647            return view.getResources().getColor(resourceId);
648        }
649    }
650
651    /** @hide */
652    protected static ColorStateList getColorStateListFromResource(View view, int resourceId) {
653        if (VERSION.SDK_INT >= VERSION_CODES.M) {
654            return view.getContext().getColorStateList(resourceId);
655        } else {
656            return view.getResources().getColorStateList(resourceId);
657        }
658    }
659
660    /** @hide */
661    protected static Drawable getDrawableFromResource(View view, int resourceId) {
662        if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
663            return view.getContext().getDrawable(resourceId);
664        } else {
665            return view.getResources().getDrawable(resourceId);
666        }
667    }
668
669    /** @hide */
670    protected static <T> T getFromArray(T[] arr, int index) {
671        if (arr == null || index < 0 || index >= arr.length) {
672            return null;
673        }
674        return arr[index];
675    }
676
677    /** @hide */
678    protected static <T> void setTo(T[] arr, int index, T value) {
679        if (arr == null || index < 0 || index >= arr.length) {
680            return;
681        }
682        arr[index] = value;
683    }
684
685    /** @hide */
686    protected static boolean getFromArray(boolean[] arr, int index) {
687        if (arr == null || index < 0 || index >= arr.length) {
688            return false;
689        }
690        return arr[index];
691    }
692
693    /** @hide */
694    protected static void setTo(boolean[] arr, int index, boolean value) {
695        if (arr == null || index < 0 || index >= arr.length) {
696            return;
697        }
698        arr[index] = value;
699    }
700
701    /** @hide */
702    protected static byte getFromArray(byte[] arr, int index) {
703        if (arr == null || index < 0 || index >= arr.length) {
704            return 0;
705        }
706        return arr[index];
707    }
708
709    /** @hide */
710    protected static void setTo(byte[] arr, int index, byte value) {
711        if (arr == null || index < 0 || index >= arr.length) {
712            return;
713        }
714        arr[index] = value;
715    }
716
717    /** @hide */
718    protected static short getFromArray(short[] arr, int index) {
719        if (arr == null || index < 0 || index >= arr.length) {
720            return 0;
721        }
722        return arr[index];
723    }
724
725    /** @hide */
726    protected static void setTo(short[] arr, int index, short value) {
727        if (arr == null || index < 0 || index >= arr.length) {
728            return;
729        }
730        arr[index] = value;
731    }
732
733    /** @hide */
734    protected static char getFromArray(char[] arr, int index) {
735        if (arr == null || index < 0 || index >= arr.length) {
736            return 0;
737        }
738        return arr[index];
739    }
740
741    /** @hide */
742    protected static void setTo(char[] arr, int index, char value) {
743        if (arr == null || index < 0 || index >= arr.length) {
744            return;
745        }
746        arr[index] = value;
747    }
748
749    /** @hide */
750    protected static int getFromArray(int[] arr, int index) {
751        if (arr == null || index < 0 || index >= arr.length) {
752            return 0;
753        }
754        return arr[index];
755    }
756
757    /** @hide */
758    protected static void setTo(int[] arr, int index, int value) {
759        if (arr == null || index < 0 || index >= arr.length) {
760            return;
761        }
762        arr[index] = value;
763    }
764
765    /** @hide */
766    protected static long getFromArray(long[] arr, int index) {
767        if (arr == null || index < 0 || index >= arr.length) {
768            return 0;
769        }
770        return arr[index];
771    }
772
773    /** @hide */
774    protected static void setTo(long[] arr, int index, long value) {
775        if (arr == null || index < 0 || index >= arr.length) {
776            return;
777        }
778        arr[index] = value;
779    }
780
781    /** @hide */
782    protected static float getFromArray(float[] arr, int index) {
783        if (arr == null || index < 0 || index >= arr.length) {
784            return 0;
785        }
786        return arr[index];
787    }
788
789    /** @hide */
790    protected static void setTo(float[] arr, int index, float value) {
791        if (arr == null || index < 0 || index >= arr.length) {
792            return;
793        }
794        arr[index] = value;
795    }
796
797    /** @hide */
798    protected static double getFromArray(double[] arr, int index) {
799        if (arr == null || index < 0 || index >= arr.length) {
800            return 0;
801        }
802        return arr[index];
803    }
804
805    /** @hide */
806    protected static void setTo(double[] arr, int index, double value) {
807        if (arr == null || index < 0 || index >= arr.length) {
808            return;
809        }
810        arr[index] = value;
811    }
812
813    /** @hide */
814    protected static <T> T getFromList(List<T> list, int index) {
815        if (list == null || index < 0 || index >= list.size()) {
816            return null;
817        }
818        return list.get(index);
819    }
820
821    /** @hide */
822    protected static <T> void setTo(List<T> list, int index, T value) {
823        if (list == null || index < 0 || index >= list.size()) {
824            return;
825        }
826        list.set(index, value);
827    }
828
829    /** @hide */
830    protected static <T> T getFromList(SparseArray<T> list, int index) {
831        if (list == null || index < 0) {
832            return null;
833        }
834        return list.get(index);
835    }
836
837    /** @hide */
838    protected static <T> void setTo(SparseArray<T> list, int index, T value) {
839        if (list == null || index < 0 || index >= list.size()) {
840            return;
841        }
842        list.put(index, value);
843    }
844
845    /** @hide */
846    @TargetApi(VERSION_CODES.JELLY_BEAN)
847    protected static <T> T getFromList(LongSparseArray<T> list, int index) {
848        if (list == null || index < 0) {
849            return null;
850        }
851        return list.get(index);
852    }
853
854    /** @hide */
855    @TargetApi(VERSION_CODES.JELLY_BEAN)
856    protected static <T> void setTo(LongSparseArray<T> list, int index, T value) {
857        if (list == null || index < 0 || index >= list.size()) {
858            return;
859        }
860        list.put(index, value);
861    }
862
863    /** @hide */
864    protected static <T> T getFromList(android.support.v4.util.LongSparseArray<T> list, int index) {
865        if (list == null || index < 0) {
866            return null;
867        }
868        return list.get(index);
869    }
870
871    /** @hide */
872    protected static <T> void setTo(android.support.v4.util.LongSparseArray<T> list, int index,
873            T value) {
874        if (list == null || index < 0 || index >= list.size()) {
875            return;
876        }
877        list.put(index, value);
878    }
879
880    /** @hide */
881    protected static boolean getFromList(SparseBooleanArray list, int index) {
882        if (list == null || index < 0) {
883            return false;
884        }
885        return list.get(index);
886    }
887
888    /** @hide */
889    protected static void setTo(SparseBooleanArray list, int index, boolean value) {
890        if (list == null || index < 0 || index >= list.size()) {
891            return;
892        }
893        list.put(index, value);
894    }
895
896    /** @hide */
897    protected static int getFromList(SparseIntArray list, int index) {
898        if (list == null || index < 0) {
899            return 0;
900        }
901        return list.get(index);
902    }
903
904    /** @hide */
905    protected static void setTo(SparseIntArray list, int index, int value) {
906        if (list == null || index < 0 || index >= list.size()) {
907            return;
908        }
909        list.put(index, value);
910    }
911
912    /** @hide */
913    @TargetApi(VERSION_CODES.JELLY_BEAN_MR2)
914    protected static long getFromList(SparseLongArray list, int index) {
915        if (list == null || index < 0) {
916            return 0;
917        }
918        return list.get(index);
919    }
920
921    /** @hide */
922    @TargetApi(VERSION_CODES.JELLY_BEAN_MR2)
923    protected static void setTo(SparseLongArray list, int index, long value) {
924        if (list == null || index < 0 || index >= list.size()) {
925            return;
926        }
927        list.put(index, value);
928    }
929
930    /** @hide */
931    protected static <K, T> T getFrom(Map<K, T> map, K key) {
932        if (map == null) {
933            return null;
934        }
935        return map.get(key);
936    }
937
938    /** @hide */
939    protected static <K, T> void setTo(Map<K, T> map, K key, T value) {
940        if (map == null) {
941            return;
942        }
943        map.put(key, value);
944    }
945
946    /** @hide */
947    protected static void setBindingInverseListener(ViewDataBinding binder,
948            InverseBindingListener oldListener, PropertyChangedInverseListener listener) {
949        if (oldListener != listener) {
950            if (oldListener != null) {
951                binder.removeOnPropertyChangedCallback(
952                        (PropertyChangedInverseListener) oldListener);
953            }
954            if (listener != null) {
955                binder.addOnPropertyChangedCallback(listener);
956            }
957        }
958    }
959
960    /**
961     * Walks the view hierarchy under roots and pulls out tagged Views, includes, and views with
962     * IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find
963     * all bound and ID'd views.
964     *
965     * @param bindingComponent The binding component to use with this binding.
966     * @param roots The root Views of the view hierarchy to walk. This is used with merge tags.
967     * @param numBindings The total number of ID'd views, views with expressions, and includes
968     * @param includes The include layout information, indexed by their container's index.
969     * @param viewsWithIds Indexes of views that don't have tags, but have IDs.
970     * @return An array of size numBindings containing all Views in the hierarchy that have IDs
971     * (with elements in viewsWithIds), are tagged containing expressions, or the bindings for
972     * included layouts.
973     * @hide
974     */
975    protected static Object[] mapBindings(DataBindingComponent bindingComponent, View[] roots,
976            int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {
977        Object[] bindings = new Object[numBindings];
978        for (int i = 0; i < roots.length; i++) {
979            mapBindings(bindingComponent, roots[i], bindings, includes, viewsWithIds, true);
980        }
981        return bindings;
982    }
983
984    private static void mapBindings(DataBindingComponent bindingComponent, View view,
985            Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
986            boolean isRoot) {
987        final int indexInIncludes;
988        final ViewDataBinding existingBinding = getBinding(view);
989        if (existingBinding != null) {
990            return;
991        }
992        Object objTag = view.getTag();
993        final String tag = (objTag instanceof String) ? (String) objTag : null;
994        boolean isBound = false;
995        if (isRoot && tag != null && tag.startsWith("layout")) {
996            final int underscoreIndex = tag.lastIndexOf('_');
997            if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) {
998                final int index = parseTagInt(tag, underscoreIndex + 1);
999                if (bindings[index] == null) {
1000                    bindings[index] = view;
1001                }
1002                indexInIncludes = includes == null ? -1 : index;
1003                isBound = true;
1004            } else {
1005                indexInIncludes = -1;
1006            }
1007        } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
1008            int tagIndex = parseTagInt(tag, BINDING_NUMBER_START);
1009            if (bindings[tagIndex] == null) {
1010                bindings[tagIndex] = view;
1011            }
1012            isBound = true;
1013            indexInIncludes = includes == null ? -1 : tagIndex;
1014        } else {
1015            // Not a bound view
1016            indexInIncludes = -1;
1017        }
1018        if (!isBound) {
1019            final int id = view.getId();
1020            if (id > 0) {
1021                int index;
1022                if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0 &&
1023                        bindings[index] == null) {
1024                    bindings[index] = view;
1025                }
1026            }
1027        }
1028
1029        if (view instanceof  ViewGroup) {
1030            final ViewGroup viewGroup = (ViewGroup) view;
1031            final int count = viewGroup.getChildCount();
1032            int minInclude = 0;
1033            for (int i = 0; i < count; i++) {
1034                final View child = viewGroup.getChildAt(i);
1035                boolean isInclude = false;
1036                if (indexInIncludes >= 0 && child.getTag() instanceof String) {
1037                    String childTag = (String) child.getTag();
1038                    if (childTag.endsWith("_0") &&
1039                            childTag.startsWith("layout") && childTag.indexOf('/') > 0) {
1040                        // This *could* be an include. Test against the expected includes.
1041                        int includeIndex = findIncludeIndex(childTag, minInclude,
1042                                includes, indexInIncludes);
1043                        if (includeIndex >= 0) {
1044                            isInclude = true;
1045                            minInclude = includeIndex + 1;
1046                            final int index = includes.indexes[indexInIncludes][includeIndex];
1047                            final int layoutId = includes.layoutIds[indexInIncludes][includeIndex];
1048                            int lastMatchingIndex = findLastMatching(viewGroup, i);
1049                            if (lastMatchingIndex == i) {
1050                                bindings[index] = DataBindingUtil.bind(bindingComponent, child,
1051                                        layoutId);
1052                            } else {
1053                                final int includeCount =  lastMatchingIndex - i + 1;
1054                                final View[] included = new View[includeCount];
1055                                for (int j = 0; j < includeCount; j++) {
1056                                    included[j] = viewGroup.getChildAt(i + j);
1057                                }
1058                                bindings[index] = DataBindingUtil.bind(bindingComponent, included,
1059                                        layoutId);
1060                                i += includeCount - 1;
1061                            }
1062                        }
1063                    }
1064                }
1065                if (!isInclude) {
1066                    mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
1067                }
1068            }
1069        }
1070    }
1071
1072    private static int findIncludeIndex(String tag, int minInclude,
1073            IncludedLayouts included, int includedIndex) {
1074        final int slashIndex = tag.indexOf('/');
1075        final CharSequence layoutName = tag.subSequence(slashIndex + 1, tag.length() - 2);
1076
1077        final String[] layouts = included.layouts[includedIndex];
1078        final int length = layouts.length;
1079        for (int i = minInclude; i < length; i++) {
1080            final String layout = layouts[i];
1081            if (TextUtils.equals(layoutName, layout)) {
1082                return i;
1083            }
1084        }
1085        return -1;
1086    }
1087
1088    private static int findLastMatching(ViewGroup viewGroup, int firstIncludedIndex) {
1089        final View firstView = viewGroup.getChildAt(firstIncludedIndex);
1090        final String firstViewTag = (String) firstView.getTag();
1091        final String tagBase = firstViewTag.substring(0, firstViewTag.length() - 1); // don't include the "0"
1092        final int tagSequenceIndex = tagBase.length();
1093
1094        final int count = viewGroup.getChildCount();
1095        int max = firstIncludedIndex;
1096        for (int i = firstIncludedIndex + 1; i < count; i++) {
1097            final View view = viewGroup.getChildAt(i);
1098            final Object objTag = view.getTag();
1099            final String tag = objTag instanceof String ? (String) view.getTag() : null;
1100            if (tag != null && tag.startsWith(tagBase)) {
1101                if (tag.length() == firstViewTag.length() && tag.charAt(tag.length() - 1) == '0') {
1102                    return max; // Found another instance of the include
1103                }
1104                if (isNumeric(tag, tagSequenceIndex)) {
1105                    max = i;
1106                }
1107            }
1108        }
1109        return max;
1110    }
1111
1112    private static boolean isNumeric(String tag, int startIndex) {
1113        int length = tag.length();
1114        if (length == startIndex) {
1115            return false; // no numerals
1116        }
1117        for (int i = startIndex; i < length; i++) {
1118            if (!Character.isDigit(tag.charAt(i))) {
1119                return false;
1120            }
1121        }
1122        return true;
1123    }
1124
1125    /**
1126     * Parse the tag without creating a new String object. This is fast and assumes the
1127     * tag is in the correct format.
1128     * @param str The tag string.
1129     * @return The binding tag number parsed from the tag string.
1130     */
1131    private static int parseTagInt(String str, int startIndex) {
1132        final int end = str.length();
1133        int val = 0;
1134        for (int i = startIndex; i < end; i++) {
1135            val *= 10;
1136            char c = str.charAt(i);
1137            val += (c - '0');
1138        }
1139        return val;
1140    }
1141
1142    private interface ObservableReference<T> {
1143        WeakListener<T> getListener();
1144        void addListener(T target);
1145        void removeListener(T target);
1146    }
1147
1148    private static class WeakListener<T> extends WeakReference<ViewDataBinding> {
1149        private final ObservableReference<T> mObservable;
1150        protected final int mLocalFieldId;
1151        private T mTarget;
1152
1153        public WeakListener(ViewDataBinding binder, int localFieldId,
1154                ObservableReference<T> observable) {
1155            super(binder);
1156            mLocalFieldId = localFieldId;
1157            mObservable = observable;
1158        }
1159
1160        public void setTarget(T object) {
1161            unregister();
1162            mTarget = object;
1163            if (mTarget != null) {
1164                mObservable.addListener(mTarget);
1165            }
1166        }
1167
1168        public boolean unregister() {
1169            boolean unregistered = false;
1170            if (mTarget != null) {
1171                mObservable.removeListener(mTarget);
1172                unregistered = true;
1173            }
1174            mTarget = null;
1175            return unregistered;
1176        }
1177
1178        public T getTarget() {
1179            return mTarget;
1180        }
1181
1182        protected ViewDataBinding getBinder() {
1183            ViewDataBinding binder = get();
1184            if (binder == null) {
1185                unregister(); // The binder is dead
1186            }
1187            return binder;
1188        }
1189    }
1190
1191    private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback
1192            implements ObservableReference<Observable> {
1193        final WeakListener<Observable> mListener;
1194
1195        public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
1196            mListener = new WeakListener<Observable>(binder, localFieldId, this);
1197        }
1198
1199        @Override
1200        public WeakListener<Observable> getListener() {
1201            return mListener;
1202        }
1203
1204        @Override
1205        public void addListener(Observable target) {
1206            target.addOnPropertyChangedCallback(this);
1207        }
1208
1209        @Override
1210        public void removeListener(Observable target) {
1211            target.removeOnPropertyChangedCallback(this);
1212        }
1213
1214        @Override
1215        public void onPropertyChanged(Observable sender, int propertyId) {
1216            ViewDataBinding binder = mListener.getBinder();
1217            if (binder == null) {
1218                return;
1219            }
1220            Observable obj = mListener.getTarget();
1221            if (obj != sender) {
1222                return; // notification from the wrong object?
1223            }
1224            binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
1225        }
1226    }
1227
1228    private static class WeakListListener extends ObservableList.OnListChangedCallback
1229            implements ObservableReference<ObservableList> {
1230        final WeakListener<ObservableList> mListener;
1231
1232        public WeakListListener(ViewDataBinding binder, int localFieldId) {
1233            mListener = new WeakListener<ObservableList>(binder, localFieldId, this);
1234        }
1235
1236        @Override
1237        public WeakListener<ObservableList> getListener() {
1238            return mListener;
1239        }
1240
1241        @Override
1242        public void addListener(ObservableList target) {
1243            target.addOnListChangedCallback(this);
1244        }
1245
1246        @Override
1247        public void removeListener(ObservableList target) {
1248            target.removeOnListChangedCallback(this);
1249        }
1250
1251        @Override
1252        public void onChanged(ObservableList sender) {
1253            ViewDataBinding binder = mListener.getBinder();
1254            if (binder == null) {
1255                return;
1256            }
1257            ObservableList target = mListener.getTarget();
1258            if (target != sender) {
1259                return; // We expect notifications only from sender
1260            }
1261            binder.handleFieldChange(mListener.mLocalFieldId, target, 0);
1262        }
1263
1264        @Override
1265        public void onItemRangeChanged(ObservableList sender, int positionStart, int itemCount) {
1266            onChanged(sender);
1267        }
1268
1269        @Override
1270        public void onItemRangeInserted(ObservableList sender, int positionStart, int itemCount) {
1271            onChanged(sender);
1272        }
1273
1274        @Override
1275        public void onItemRangeMoved(ObservableList sender, int fromPosition, int toPosition,
1276                int itemCount) {
1277            onChanged(sender);
1278        }
1279
1280        @Override
1281        public void onItemRangeRemoved(ObservableList sender, int positionStart, int itemCount) {
1282            onChanged(sender);
1283        }
1284    }
1285
1286    private static class WeakMapListener extends ObservableMap.OnMapChangedCallback
1287            implements ObservableReference<ObservableMap> {
1288        final WeakListener<ObservableMap> mListener;
1289
1290        public WeakMapListener(ViewDataBinding binder, int localFieldId) {
1291            mListener = new WeakListener<ObservableMap>(binder, localFieldId, this);
1292        }
1293
1294        @Override
1295        public WeakListener<ObservableMap> getListener() {
1296            return mListener;
1297        }
1298
1299        @Override
1300        public void addListener(ObservableMap target) {
1301            target.addOnMapChangedCallback(this);
1302        }
1303
1304        @Override
1305        public void removeListener(ObservableMap target) {
1306            target.removeOnMapChangedCallback(this);
1307        }
1308
1309        @Override
1310        public void onMapChanged(ObservableMap sender, Object key) {
1311            ViewDataBinding binder = mListener.getBinder();
1312            if (binder == null || sender != mListener.getTarget()) {
1313                return;
1314            }
1315            binder.handleFieldChange(mListener.mLocalFieldId, sender, 0);
1316        }
1317    }
1318
1319    private interface CreateWeakListener {
1320        WeakListener create(ViewDataBinding viewDataBinding, int localFieldId);
1321    }
1322
1323    /**
1324     * This class is used by generated subclasses of {@link ViewDataBinding} to track the
1325     * included layouts contained in the bound layout. This class is an implementation
1326     * detail of how binding expressions are mapped to Views after inflation.
1327     * @hide
1328     */
1329    protected static class IncludedLayouts {
1330        public final String[][] layouts;
1331        public final int[][] indexes;
1332        public final int[][] layoutIds;
1333
1334        public IncludedLayouts(int bindingCount) {
1335            layouts = new String[bindingCount][];
1336            indexes = new int[bindingCount][];
1337            layoutIds = new int[bindingCount][];
1338        }
1339
1340        public void setIncludes(int index, String[] layouts, int[] indexes, int[] layoutIds) {
1341            this.layouts[index] = layouts;
1342            this.indexes[index] = indexes;
1343            this.layoutIds[index] = layoutIds;
1344        }
1345    }
1346
1347    /**
1348     * This class is used by generated subclasses of {@link ViewDataBinding} to listen for
1349     * changes on variables of Bindings. This is important for two-way data binding on variables
1350     * in included Bindings.
1351     * @hide
1352     */
1353    protected static abstract class PropertyChangedInverseListener
1354            extends Observable.OnPropertyChangedCallback implements InverseBindingListener {
1355        final int mPropertyId;
1356
1357        public PropertyChangedInverseListener(int propertyId) {
1358            mPropertyId = propertyId;
1359        }
1360
1361        @Override
1362        public void onPropertyChanged(Observable sender, int propertyId) {
1363            if (propertyId == mPropertyId || propertyId == 0) {
1364                onChange();
1365            }
1366        }
1367    }
1368}
1369