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