ViewDataBinding.java revision 55ee815547c229655ac72674ab1a994917fcf477
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.databinding.CallbackRegistry.NotifierCallback;
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.SparseIntArray;
29import android.view.Choreographer;
30import android.view.View;
31import android.view.View.OnAttachStateChangeListener;
32import android.view.ViewGroup;
33
34import java.lang.ref.WeakReference;
35
36public abstract class ViewDataBinding {
37
38    /**
39     * Instead of directly accessing Build.VERSION.SDK_INT, generated code uses this value so that
40     * we can test API dependent behavior.
41     */
42    static int SDK_INT = VERSION.SDK_INT;
43
44    private static final int REBIND = 1;
45    private static final int HALTED = 2;
46    private static final int REBOUND = 3;
47
48    /**
49     * Prefix for android:tag on Views with binding. The root View and include tags will not have
50     * android:tag attributes and will use ids instead.
51     */
52    public static final String BINDING_TAG_PREFIX = "binding_";
53
54    // The length of BINDING_TAG_PREFIX prevents calling length repeatedly.
55    private static final int BINDING_NUMBER_START = BINDING_TAG_PREFIX.length();
56
57    // ICS (v 14) fixes a leak when using setTag(int, Object)
58    private static final boolean USE_TAG_ID = DataBinderMapper.TARGET_MIN_SDK >= 14;
59
60    private static final boolean USE_CHOREOGRAPHER = SDK_INT >= 16;
61
62    /**
63     * Method object extracted out to attach a listener to a bound Observable object.
64     */
65    private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
66        @Override
67        public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
68            return new WeakPropertyListener(viewDataBinding, localFieldId);
69        }
70    };
71
72    /**
73     * Method object extracted out to attach a listener to a bound ObservableList object.
74     */
75    private static final CreateWeakListener CREATE_LIST_LISTENER = new CreateWeakListener() {
76        @Override
77        public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
78            return new WeakListListener(viewDataBinding, localFieldId);
79        }
80    };
81
82    /**
83     * Method object extracted out to attach a listener to a bound ObservableMap object.
84     */
85    private static final CreateWeakListener CREATE_MAP_LISTENER = new CreateWeakListener() {
86        @Override
87        public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
88            return new WeakMapListener(viewDataBinding, localFieldId);
89        }
90    };
91
92    private static final CallbackRegistry.NotifierCallback<OnRebindCallback, ViewDataBinding, Void>
93        REBIND_NOTIFIER = new NotifierCallback<OnRebindCallback, ViewDataBinding, Void>() {
94        @Override
95        public void onNotifyCallback(OnRebindCallback callback, ViewDataBinding sender, int mode,
96                Void arg2) {
97            switch (mode) {
98                case REBIND:
99                    if (!callback.onPreBind(sender)) {
100                        sender.mRebindHalted = true;
101                    }
102                    break;
103                case HALTED:
104                    callback.onCanceled(sender);
105                    break;
106                case REBOUND:
107                    callback.onBound(sender);
108                    break;
109            }
110        }
111    };
112
113    private static final OnAttachStateChangeListener ROOT_REATTACHED_LISTENER;
114
115    static {
116        if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {
117            ROOT_REATTACHED_LISTENER = null;
118        } else {
119            ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() {
120                @TargetApi(VERSION_CODES.KITKAT)
121                @Override
122                public void onViewAttachedToWindow(View v) {
123                    // execute the pending bindings.
124                    final ViewDataBinding binding;
125                    if (USE_TAG_ID) {
126                        binding = (ViewDataBinding) v.getTag(R.id.dataBinding);
127                    } else {
128                        binding = (ViewDataBinding) v.getTag();
129                    }
130                    binding.mRebindRunnable.run();
131                    v.removeOnAttachStateChangeListener(this);
132                }
133
134                @Override
135                public void onViewDetachedFromWindow(View v) {
136                }
137            };
138        }
139    }
140
141    /**
142     * Runnable executed on animation heartbeat to rebind the dirty Views.
143     */
144    private final Runnable mRebindRunnable = new Runnable() {
145        @Override
146        public void run() {
147            synchronized (this) {
148                mPendingRebind = false;
149            }
150            if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
151                // Nested so that we don't get a lint warning in IntelliJ
152                if (!mRoot.isAttachedToWindow()) {
153                    // Don't execute the pending bindings until the View
154                    // is attached again.
155                    mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
156                    mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
157                    return;
158                }
159            }
160            executePendingBindings();
161        }
162    };
163
164    /**
165     * Flag indicates that there are pending bindings that need to be reevaluated.
166     */
167    private boolean mPendingRebind = false;
168
169    /**
170     * Indicates that a onPreBind has stopped the executePendingBindings call.
171     */
172    private boolean mRebindHalted = false;
173
174    /**
175     * The observed expressions.
176     */
177    private WeakListener[] mLocalFieldObservers;
178
179    /**
180     * The root View that this Binding is associated with.
181     */
182    private final View mRoot;
183
184    /**
185     * The collection of OnRebindCallbacks.
186     */
187    private CallbackRegistry<OnRebindCallback, ViewDataBinding, Void> mRebindCallbacks;
188
189    /**
190     * Flag to prevent reentrant executePendingBinding calls.
191     */
192    private boolean mIsExecutingPendingBindings;
193
194    // null api < 16
195    private Choreographer mChoreographer;
196
197    private final Choreographer.FrameCallback mFrameCallback;
198
199    // null api >= 16
200    private Handler mUIThreadHandler;
201
202    protected ViewDataBinding(View root, int localFieldCount) {
203        mLocalFieldObservers = new WeakListener[localFieldCount];
204        this.mRoot = root;
205        if (Looper.myLooper() == null) {
206            throw new IllegalStateException("DataBinding must be created in view's UI Thread");
207        }
208        if (USE_CHOREOGRAPHER) {
209            mChoreographer = Choreographer.getInstance();
210            mFrameCallback = new Choreographer.FrameCallback() {
211                @Override
212                public void doFrame(long frameTimeNanos) {
213                    mRebindRunnable.run();
214                }
215            };
216        } else {
217            mFrameCallback = null;
218            mUIThreadHandler = new Handler(Looper.myLooper());
219        }
220        requestRebind();
221    }
222
223    protected void setRootTag(View view) {
224        if (USE_TAG_ID) {
225            view.setTag(R.id.dataBinding, this);
226        } else {
227            view.setTag(this);
228        }
229    }
230
231    protected void setRootTag(View[] views) {
232        if (USE_TAG_ID) {
233            for (View view : views) {
234                view.setTag(R.id.dataBinding, this);
235            }
236        } else {
237            for (View view : views) {
238                view.setTag(this);
239            }
240        }
241    }
242
243    public static int getBuildSdkInt() {
244        return SDK_INT;
245    }
246
247    /**
248     * Called when an observed object changes. Sets the appropriate dirty flag if applicable.
249     * @param localFieldId The index into mLocalFieldObservers that this Object resides in.
250     * @param object The object that has changed.
251     * @param fieldId The BR ID of the field being changed or _all if
252     *                no specific field is being notified.
253     * @return true if this change should cause a change to the UI.
254     */
255    protected abstract boolean onFieldChange(int localFieldId, Object object, int fieldId);
256
257    /**
258     * Set a value value in the Binding class.
259     * <p>
260     * Typically, the developer will be able to call the subclass's set method directly. For
261     * example, if there is a variable <code>x</code> in the Binding, a <code>setX</code> method
262     * will be generated. However, there are times when the specific subclass of ViewDataBinding
263     * is unknown, so the generated method cannot be discovered without reflection. The
264     * setVariable call allows the values of variables to be set without reflection.
265     *
266     * @param variableId the BR id of the variable to be set. For example, if the variable is
267     *                   <code>x</code>, then variableId will be <code>BR.x</code>.
268     * @param value The new value of the variable to be set.
269     * @return <code>true</code> if the variable exists in the binding or <code>false</code>
270     * otherwise.
271     */
272    public abstract boolean setVariable(int variableId, Object value);
273
274    /**
275     * Add a listener to be called when reevaluating dirty fields. This also allows automatic
276     * updates to be halted, but does not stop explicit calls to {@link #executePendingBindings()}.
277     *
278     * @param listener The listener to add.
279     */
280    public void addOnRebindCallback(OnRebindCallback listener) {
281        if (mRebindCallbacks == null) {
282            mRebindCallbacks = new CallbackRegistry<OnRebindCallback, ViewDataBinding, Void>(REBIND_NOTIFIER);
283        }
284        mRebindCallbacks.add(listener);
285    }
286
287    /**
288     * Removes a listener that was added in {@link #addOnRebindCallback(OnRebindCallback)}.
289     *
290     * @param listener The listener to remove.
291     */
292    public void removeOnRebindCallback(OnRebindCallback listener) {
293        if (mRebindCallbacks != null) {
294            mRebindCallbacks.remove(listener);
295        }
296    }
297
298    /**
299     * Evaluates the pending bindings, updating any Views that have expressions bound to
300     * modified variables. This <b>must</b> be run on the UI thread.
301     */
302    public void executePendingBindings() {
303        if (mIsExecutingPendingBindings) {
304            requestRebind();
305            return;
306        }
307        mIsExecutingPendingBindings = true;
308        mRebindHalted = false;
309        if (mRebindCallbacks != null) {
310            mRebindCallbacks.notifyCallbacks(this, REBIND, null);
311
312            // The onRebindListeners will change mPendingHalted
313            if (mRebindHalted) {
314                mRebindCallbacks.notifyCallbacks(this, HALTED, null);
315            }
316        }
317        if (!mRebindHalted) {
318            executeBindings();
319            if (mRebindCallbacks != null) {
320                mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
321            }
322        }
323        mIsExecutingPendingBindings = false;
324    }
325
326    void forceExecuteBindings() {
327        executeBindings();
328    }
329
330    protected abstract void executeBindings();
331
332    /**
333     * Used internally to invalidate flags of included layouts.
334     */
335    public abstract void invalidateAll();
336
337    /**
338     * Removes binding listeners to expression variables.
339     */
340    public void unbind() {
341        for (WeakListener weakListener : mLocalFieldObservers) {
342            if (weakListener != null) {
343                weakListener.unregister();
344            }
345        }
346    }
347
348    @Override
349    protected void finalize() throws Throwable {
350        unbind();
351    }
352
353    static ViewDataBinding getBinding(View v) {
354        if (USE_TAG_ID) {
355            return (ViewDataBinding) v.getTag(R.id.dataBinding);
356        } else {
357            final Object tag = v.getTag();
358            if (tag instanceof ViewDataBinding) {
359                return (ViewDataBinding) tag;
360            }
361        }
362        return null;
363    }
364
365    /**
366     * Returns the outermost View in the layout file associated with the Binding.
367     * @return the outermost View in the layout file associated with the Binding.
368     */
369    public View getRoot() {
370        return mRoot;
371    }
372
373    private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
374        boolean result = onFieldChange(mLocalFieldId, object, fieldId);
375        if (result) {
376            requestRebind();
377        }
378    }
379
380    protected boolean unregisterFrom(int localFieldId) {
381        WeakListener listener = mLocalFieldObservers[localFieldId];
382        if (listener != null) {
383            return listener.unregister();
384        }
385        return false;
386    }
387
388    protected void requestRebind() {
389        synchronized (this) {
390            if (mPendingRebind) {
391                return;
392            }
393            mPendingRebind = true;
394        }
395        if (USE_CHOREOGRAPHER) {
396            mChoreographer.postFrameCallback(mFrameCallback);
397        } else {
398            mUIThreadHandler.post(mRebindRunnable);
399        }
400
401    }
402
403    protected Object getObservedField(int localFieldId) {
404        WeakListener listener = mLocalFieldObservers[localFieldId];
405        if (listener == null) {
406            return null;
407        }
408        return listener.getTarget();
409    }
410
411    private boolean updateRegistration(int localFieldId, Object observable,
412            CreateWeakListener listenerCreator) {
413        if (observable == null) {
414            return unregisterFrom(localFieldId);
415        }
416        WeakListener listener = mLocalFieldObservers[localFieldId];
417        if (listener == null) {
418            registerTo(localFieldId, observable, listenerCreator);
419            return true;
420        }
421        if (listener.getTarget() == observable) {
422            return false;//nothing to do, same object
423        }
424        unregisterFrom(localFieldId);
425        registerTo(localFieldId, observable, listenerCreator);
426        return true;
427    }
428
429    protected boolean updateRegistration(int localFieldId, Observable observable) {
430        return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
431    }
432
433    protected boolean updateRegistration(int localFieldId, ObservableList observable) {
434        return updateRegistration(localFieldId, observable, CREATE_LIST_LISTENER);
435    }
436
437    protected boolean updateRegistration(int localFieldId, ObservableMap observable) {
438        return updateRegistration(localFieldId, observable, CREATE_MAP_LISTENER);
439    }
440
441    protected void registerTo(int localFieldId, Object observable,
442            CreateWeakListener listenerCreator) {
443        if (observable == null) {
444            return;
445        }
446        WeakListener listener = mLocalFieldObservers[localFieldId];
447        if (listener == null) {
448            listener = listenerCreator.create(this, localFieldId);
449            mLocalFieldObservers[localFieldId] = listener;
450        }
451        listener.setTarget(observable);
452    }
453
454    protected static ViewDataBinding bind(View view, int layoutId) {
455        return DataBindingUtil.bind(view, layoutId);
456    }
457
458    /**
459     * Walks the view hierarchy under root and pulls out tagged Views, includes, and views with
460     * IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find
461     * all bound and ID'd views.
462     *
463     * @param root The root of the view hierarchy to walk.
464     * @param numBindings The total number of ID'd views, views with expressions, and includes
465     * @param includes The include layout information, indexed by their container's index.
466     * @param viewsWithIds Indexes of views that don't have tags, but have IDs.
467     * @return An array of size numBindings containing all Views in the hierarchy that have IDs
468     * (with elements in viewsWithIds), are tagged containing expressions, or the bindings for
469     * included layouts.
470     */
471    protected static Object[] mapBindings(View root, int numBindings,
472            IncludedLayoutIndex[][] includes, SparseIntArray viewsWithIds) {
473        Object[] bindings = new Object[numBindings];
474        mapBindings(root, bindings, includes, viewsWithIds, true);
475        return bindings;
476    }
477
478    /**
479     * Walks the view hierarchy under roots and pulls out tagged Views, includes, and views with
480     * IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find
481     * all bound and ID'd views.
482     *
483     * @param roots The root Views of the view hierarchy to walk. This is used with merge tags.
484     * @param numBindings The total number of ID'd views, views with expressions, and includes
485     * @param includes The include layout information, indexed by their container's index.
486     * @param viewsWithIds Indexes of views that don't have tags, but have IDs.
487     * @return An array of size numBindings containing all Views in the hierarchy that have IDs
488     * (with elements in viewsWithIds), are tagged containing expressions, or the bindings for
489     * included layouts.
490     */
491    protected static Object[] mapBindings(View[] roots, int numBindings,
492            IncludedLayoutIndex[][] includes, SparseIntArray viewsWithIds) {
493        Object[] bindings = new Object[numBindings];
494        for (int i = 0; i < roots.length; i++) {
495            mapBindings(roots[i], bindings, includes, viewsWithIds, true);
496        }
497        return bindings;
498    }
499
500    private static void mapBindings(View view, Object[] bindings,
501            IncludedLayoutIndex[][] includes, SparseIntArray viewsWithIds, boolean isRoot) {
502        final IncludedLayoutIndex[] includedLayoutIndexes;
503        final String tag = (String) view.getTag();
504        boolean isBound = false;
505        if (isRoot && tag != null && tag.startsWith("layout")) {
506            final int underscoreIndex = tag.lastIndexOf('_');
507            if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) {
508                final int index = parseTagInt(tag, underscoreIndex + 1);
509                bindings[index] = view;
510                includedLayoutIndexes = includes == null ? null : includes[index];
511                isBound = true;
512            } else {
513                includedLayoutIndexes = null;
514            }
515        } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
516            int tagIndex = parseTagInt(tag, BINDING_NUMBER_START);
517            bindings[tagIndex] = view;
518            isBound = true;
519            includedLayoutIndexes = includes == null ? null : includes[tagIndex];
520        } else {
521            // Not a bound view
522            includedLayoutIndexes = null;
523        }
524        if (!isBound) {
525            final int id = view.getId();
526            if (id > 0) {
527                int index;
528                if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0) {
529                    bindings[index] = view;
530                }
531            }
532        }
533
534        if (view instanceof  ViewGroup) {
535            final ViewGroup viewGroup = (ViewGroup) view;
536            final int count = viewGroup.getChildCount();
537            int minInclude = 0;
538            for (int i = 0; i < count; i++) {
539                final View child = viewGroup.getChildAt(i);
540                boolean isInclude = false;
541                if (includedLayoutIndexes != null) {
542                    String childTag = (String) child.getTag();
543                    if (childTag != null && childTag.endsWith("_0") &&
544                            childTag.startsWith("layout") && childTag.indexOf('/') > 0) {
545                        // This *could* be an include. Test against the expected includes.
546                        int includeIndex = findIncludeIndex(childTag, minInclude,
547                                includedLayoutIndexes);
548                        if (includeIndex >= 0) {
549                            isInclude = true;
550                            minInclude = includeIndex + 1;
551                            IncludedLayoutIndex include = includedLayoutIndexes[includeIndex];
552                            int lastMatchingIndex = findLastMatching(viewGroup, i);
553                            if (lastMatchingIndex == i) {
554                                bindings[include.index] = DataBindingUtil.bind(child,
555                                        include.layoutId);
556                            } else {
557                                final int includeCount =  lastMatchingIndex - i + 1;
558                                final View[] included = new View[includeCount];
559                                for (int j = 0; j < includeCount; j++) {
560                                    included[j] = viewGroup.getChildAt(i + j);
561                                }
562                                bindings[include.index] = DataBindingUtil.bind(included,
563                                        include.layoutId);
564                                i += includeCount - 1;
565                            }
566                        }
567                    }
568                }
569                if (!isInclude) {
570                    mapBindings(child, bindings, includes, viewsWithIds, false);
571                }
572            }
573        }
574    }
575
576    private static int findIncludeIndex(String tag, int minInclude,
577            IncludedLayoutIndex[] layoutIndexes) {
578        final int slashIndex = tag.indexOf('/');
579        final CharSequence layoutName = tag.subSequence(slashIndex + 1, tag.length() - 2);
580
581        final int length = layoutIndexes.length;
582        for (int i = minInclude; i < length; i++) {
583            final IncludedLayoutIndex layoutIndex = layoutIndexes[i];
584            if (TextUtils.equals(layoutName, layoutIndex.layout)) {
585                return i;
586            }
587        }
588        return -1;
589    }
590
591    private static int findLastMatching(ViewGroup viewGroup, int firstIncludedIndex) {
592        final View firstView = viewGroup.getChildAt(firstIncludedIndex);
593        final String firstViewTag = (String) firstView.getTag();
594        final String tagBase = firstViewTag.substring(0, firstViewTag.length() - 1); // don't include the "0"
595        final int tagSequenceIndex = tagBase.length();
596
597        final int count = viewGroup.getChildCount();
598        int max = firstIncludedIndex;
599        for (int i = firstIncludedIndex + 1; i < count; i++) {
600            final View view = viewGroup.getChildAt(i);
601            final String tag = (String) view.getTag();
602            if (tag != null && tag.startsWith(tagBase)) {
603                if (tag.length() == firstViewTag.length() && tag.charAt(tag.length() - 1) == '0') {
604                    return max; // Found another instance of the include
605                }
606                if (isNumeric(tag, tagSequenceIndex)) {
607                    max = i;
608                }
609            }
610        }
611        return max;
612    }
613
614    private static boolean isNumeric(String tag, int startIndex) {
615        int length = tag.length();
616        if (length == startIndex) {
617            return false; // no numerals
618        }
619        for (int i = startIndex; i < length; i++) {
620            if (!Character.isDigit(tag.charAt(i))) {
621                return false;
622            }
623        }
624        return true;
625    }
626
627    /**
628     * Parse the tag without creating a new String object. This is fast and assumes the
629     * tag is in the correct format.
630     * @param str The tag string.
631     * @return The binding tag number parsed from the tag string.
632     */
633    private static int parseTagInt(String str, int startIndex) {
634        final int end = str.length();
635        int val = 0;
636        for (int i = startIndex; i < end; i++) {
637            val *= 10;
638            char c = str.charAt(i);
639            val += (c - '0');
640        }
641        return val;
642    }
643
644    private static abstract class WeakListener<T> {
645        private final WeakReference<ViewDataBinding> mBinder;
646        protected final int mLocalFieldId;
647        private T mTarget;
648
649        public WeakListener(ViewDataBinding binder, int localFieldId) {
650            mBinder = new WeakReference<ViewDataBinding>(binder);
651            mLocalFieldId = localFieldId;
652        }
653
654        public void setTarget(T object) {
655            unregister();
656            mTarget = object;
657            if (mTarget != null) {
658                addListener(mTarget);
659            }
660        }
661
662        public boolean unregister() {
663            boolean unregistered = false;
664            if (mTarget != null) {
665                removeListener(mTarget);
666                unregistered = true;
667            }
668            mTarget = null;
669            return unregistered;
670        }
671
672        public T getTarget() {
673            return mTarget;
674        }
675
676        protected ViewDataBinding getBinder() {
677            ViewDataBinding binder = mBinder.get();
678            if (binder == null) {
679                unregister(); // The binder is dead
680            }
681            return binder;
682        }
683
684        protected abstract void addListener(T target);
685        protected abstract void removeListener(T target);
686    }
687
688    private static class WeakPropertyListener extends WeakListener<Observable>
689            implements OnPropertyChangedListener {
690        public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
691            super(binder, localFieldId);
692        }
693
694        @Override
695        protected void addListener(Observable target) {
696            target.addOnPropertyChangedListener(this);
697        }
698
699        @Override
700        protected void removeListener(Observable target) {
701            target.removeOnPropertyChangedListener(this);
702        }
703
704        @Override
705        public void onPropertyChanged(Observable sender, int fieldId) {
706            ViewDataBinding binder = getBinder();
707            if (binder == null) {
708                return;
709            }
710            Observable obj = getTarget();
711            if (obj != sender) {
712                return; // notification from the wrong object?
713            }
714            binder.handleFieldChange(mLocalFieldId, sender, fieldId);
715        }
716    }
717
718    private static class WeakListListener extends WeakListener<ObservableList>
719            implements OnListChangedListener {
720
721        public WeakListListener(ViewDataBinding binder, int localFieldId) {
722            super(binder, localFieldId);
723        }
724
725        @Override
726        public void onChanged() {
727            ViewDataBinding binder = getBinder();
728            if (binder == null) {
729                return;
730            }
731            ObservableList target = getTarget();
732            if (target == null) {
733                return; // We don't expect any notifications from null targets
734            }
735            binder.handleFieldChange(mLocalFieldId, target, 0);
736        }
737
738        @Override
739        public void onItemRangeChanged(int positionStart, int itemCount) {
740            onChanged();
741        }
742
743        @Override
744        public void onItemRangeInserted(int positionStart, int itemCount) {
745            onChanged();
746        }
747
748        @Override
749        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
750            onChanged();
751        }
752
753        @Override
754        public void onItemRangeRemoved(int positionStart, int itemCount) {
755            onChanged();
756        }
757
758        @Override
759        protected void addListener(ObservableList target) {
760            target.addOnListChangedListener(this);
761        }
762
763        @Override
764        protected void removeListener(ObservableList target) {
765            target.removeOnListChangedListener(this);
766        }
767    }
768
769    private static class WeakMapListener extends WeakListener<ObservableMap>
770            implements OnMapChangedListener {
771        public WeakMapListener(ViewDataBinding binder, int localFieldId) {
772            super(binder, localFieldId);
773        }
774
775        @Override
776        protected void addListener(ObservableMap target) {
777            target.addOnMapChangedListener(this);
778        }
779
780        @Override
781        protected void removeListener(ObservableMap target) {
782            target.removeOnMapChangedListener(this);
783        }
784
785        @Override
786        public void onMapChanged(ObservableMap sender, Object key) {
787            ViewDataBinding binder = getBinder();
788            if (binder == null || sender != getTarget()) {
789                return;
790            }
791            binder.handleFieldChange(mLocalFieldId, sender, 0);
792        }
793    }
794
795    private interface CreateWeakListener {
796        WeakListener create(ViewDataBinding viewDataBinding, int localFieldId);
797    }
798
799    protected static class IncludedLayoutIndex {
800        public final String layout;
801        public final int index;
802        public final int layoutId;
803
804        public IncludedLayoutIndex(String layout, int index, int layoutId) {
805            this.layout = layout;
806            this.index = index;
807            this.layoutId = layoutId;
808        }
809    }
810}
811