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