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