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