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