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