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 
17 package android.databinding;
18 
19 import com.android.databinding.library.R;
20 
21 import android.annotation.TargetApi;
22 import android.databinding.CallbackRegistry.NotifierCallback;
23 import android.os.Build.VERSION;
24 import android.os.Build.VERSION_CODES;
25 import android.os.Handler;
26 import android.os.Looper;
27 import android.text.TextUtils;
28 import android.util.SparseIntArray;
29 import android.view.Choreographer;
30 import android.view.LayoutInflater;
31 import android.view.View;
32 import android.view.View.OnAttachStateChangeListener;
33 import android.view.ViewGroup;
34 
35 import 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  */
43 public 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      */
ViewDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount)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      */
setRootTag(View view)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      */
setRootTag(View[] views)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      */
getBuildSdkInt()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      */
onFieldChange(int localFieldId, Object object, int fieldId)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      */
setVariable(int variableId, Object value)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      */
addOnRebindCallback(OnRebindCallback listener)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      */
removeOnRebindCallback(OnRebindCallback listener)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      */
executePendingBindings()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 
forceExecuteBindings()355     void forceExecuteBindings() {
356         executeBindings();
357     }
358 
359     /**
360      * @hide
361      */
executeBindings()362     protected abstract void executeBindings();
363 
364     /**
365      * Invalidates all binding expressions and requests a new rebind to refresh UI.
366      */
invalidateAll()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      */
hasPendingBindings()374     public abstract boolean hasPendingBindings();
375 
376     /**
377      * Removes binding listeners to expression variables.
378      */
unbind()379     public void unbind() {
380         for (WeakListener weakListener : mLocalFieldObservers) {
381             if (weakListener != null) {
382                 weakListener.unregister();
383             }
384         }
385     }
386 
387     @Override
finalize()388     protected void finalize() throws Throwable {
389         unbind();
390     }
391 
getBinding(View v)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      */
getRoot()412     public View getRoot() {
413         return mRoot;
414     }
415 
handleFieldChange(int mLocalFieldId, Object object, int fieldId)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      */
unregisterFrom(int localFieldId)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      */
requestRebind()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      */
getObservedField(int localFieldId)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 
updateRegistration(int localFieldId, Object observable, CreateWeakListener listenerCreator)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      */
updateRegistration(int localFieldId, Observable observable)484     protected boolean updateRegistration(int localFieldId, Observable observable) {
485         return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
486     }
487 
488     /**
489      * @hide
490      */
updateRegistration(int localFieldId, ObservableList observable)491     protected boolean updateRegistration(int localFieldId, ObservableList observable) {
492         return updateRegistration(localFieldId, observable, CREATE_LIST_LISTENER);
493     }
494 
495     /**
496      * @hide
497      */
updateRegistration(int localFieldId, ObservableMap observable)498     protected boolean updateRegistration(int localFieldId, ObservableMap observable) {
499         return updateRegistration(localFieldId, observable, CREATE_MAP_LISTENER);
500     }
501 
502     /**
503      * @hide
504      */
ensureBindingComponentIsNotNull(Class<?> oneExample)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      */
registerTo(int localFieldId, Object observable, CreateWeakListener listenerCreator)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      */
bind(DataBindingComponent bindingComponent, View view, int layoutId)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      */
mapBindings(DataBindingComponent bindingComponent, View root, int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds)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      */
mapBindings(DataBindingComponent bindingComponent, View[] roots, int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds)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 
mapBindings(DataBindingComponent bindingComponent, View view, Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds, boolean isRoot)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 
findIncludeIndex(String tag, int minInclude, IncludedLayouts included, int includedIndex)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 
findLastMatching(ViewGroup viewGroup, int firstIncludedIndex)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 
isNumeric(String tag, int startIndex)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      */
parseTagInt(String str, int startIndex)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> {
getListener()745         WeakListener<T> getListener();
addListener(T target)746         void addListener(T target);
removeListener(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 
WeakListener(ViewDataBinding binder, int localFieldId, ObservableReference<T> observable)755         public WeakListener(ViewDataBinding binder, int localFieldId,
756                 ObservableReference<T> observable) {
757             super(binder);
758             mLocalFieldId = localFieldId;
759             mObservable = observable;
760         }
761 
setTarget(T object)762         public void setTarget(T object) {
763             unregister();
764             mTarget = object;
765             if (mTarget != null) {
766                 mObservable.addListener(mTarget);
767             }
768         }
769 
unregister()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 
getTarget()780         public T getTarget() {
781             return mTarget;
782         }
783 
getBinder()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 
WeakPropertyListener(ViewDataBinding binder, int localFieldId)797         public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
798             mListener = new WeakListener<Observable>(binder, localFieldId, this);
799         }
800 
801         @Override
getListener()802         public WeakListener<Observable> getListener() {
803             return mListener;
804         }
805 
806         @Override
addListener(Observable target)807         public void addListener(Observable target) {
808             target.addOnPropertyChangedCallback(this);
809         }
810 
811         @Override
removeListener(Observable target)812         public void removeListener(Observable target) {
813             target.removeOnPropertyChangedCallback(this);
814         }
815 
816         @Override
onPropertyChanged(Observable sender, int propertyId)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 
WeakListListener(ViewDataBinding binder, int localFieldId)834         public WeakListListener(ViewDataBinding binder, int localFieldId) {
835             mListener = new WeakListener<ObservableList>(binder, localFieldId, this);
836         }
837 
838         @Override
getListener()839         public WeakListener<ObservableList> getListener() {
840             return mListener;
841         }
842 
843         @Override
addListener(ObservableList target)844         public void addListener(ObservableList target) {
845             target.addOnListChangedCallback(this);
846         }
847 
848         @Override
removeListener(ObservableList target)849         public void removeListener(ObservableList target) {
850             target.removeOnListChangedCallback(this);
851         }
852 
853         @Override
onChanged(ObservableList sender)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
onItemRangeChanged(ObservableList sender, int positionStart, int itemCount)867         public void onItemRangeChanged(ObservableList sender, int positionStart, int itemCount) {
868             onChanged(sender);
869         }
870 
871         @Override
onItemRangeInserted(ObservableList sender, int positionStart, int itemCount)872         public void onItemRangeInserted(ObservableList sender, int positionStart, int itemCount) {
873             onChanged(sender);
874         }
875 
876         @Override
onItemRangeMoved(ObservableList sender, int fromPosition, int toPosition, int itemCount)877         public void onItemRangeMoved(ObservableList sender, int fromPosition, int toPosition,
878                 int itemCount) {
879             onChanged(sender);
880         }
881 
882         @Override
onItemRangeRemoved(ObservableList sender, int positionStart, int itemCount)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 
WeakMapListener(ViewDataBinding binder, int localFieldId)892         public WeakMapListener(ViewDataBinding binder, int localFieldId) {
893             mListener = new WeakListener<ObservableMap>(binder, localFieldId, this);
894         }
895 
896         @Override
getListener()897         public WeakListener<ObservableMap> getListener() {
898             return mListener;
899         }
900 
901         @Override
addListener(ObservableMap target)902         public void addListener(ObservableMap target) {
903             target.addOnMapChangedCallback(this);
904         }
905 
906         @Override
removeListener(ObservableMap target)907         public void removeListener(ObservableMap target) {
908             target.removeOnMapChangedCallback(this);
909         }
910 
911         @Override
onMapChanged(ObservableMap sender, Object key)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 {
create(ViewDataBinding viewDataBinding, int localFieldId)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 
IncludedLayouts(int bindingCount)936         public IncludedLayouts(int bindingCount) {
937             layouts = new String[bindingCount][];
938             indexes = new int[bindingCount][];
939             layoutIds = new int[bindingCount][];
940         }
941 
setIncludes(int index, String[] layouts, int[] indexes, int[] layoutIds)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