• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.annotation.TargetApi;
20 import android.content.res.ColorStateList;
21 import android.databinding.CallbackRegistry.NotifierCallback;
22 import android.graphics.drawable.Drawable;
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.LongSparseArray;
29 import android.util.SparseArray;
30 import android.util.SparseBooleanArray;
31 import android.util.SparseIntArray;
32 import android.util.SparseLongArray;
33 import android.view.Choreographer;
34 import android.view.LayoutInflater;
35 import android.view.View;
36 import android.view.View.OnAttachStateChangeListener;
37 import android.view.ViewGroup;
38 
39 import com.android.databinding.library.R;
40 
41 import java.lang.ref.WeakReference;
42 import java.util.List;
43 import java.util.Map;
44 
45 /**
46  * Base class for generated data binding classes. If possible, the generated binding should
47  * be instantiated using one of its generated static bind or inflate methods. If the specific
48  * binding is unknown, {@link DataBindingUtil#bind(View)} or
49  * {@link DataBindingUtil#inflate(LayoutInflater, int, ViewGroup, boolean)} should be used.
50  */
51 public abstract class ViewDataBinding extends BaseObservable {
52 
53     /**
54      * Instead of directly accessing Build.VERSION.SDK_INT, generated code uses this value so that
55      * we can test API dependent behavior.
56      */
57     static int SDK_INT = VERSION.SDK_INT;
58 
59     private static final int REBIND = 1;
60     private static final int HALTED = 2;
61     private static final int REBOUND = 3;
62 
63     /**
64      * Prefix for android:tag on Views with binding. The root View and include tags will not have
65      * android:tag attributes and will use ids instead.
66      *
67      * @hide
68      */
69     public static final String BINDING_TAG_PREFIX = "binding_";
70 
71     // The length of BINDING_TAG_PREFIX prevents calling length repeatedly.
72     private static final int BINDING_NUMBER_START = BINDING_TAG_PREFIX.length();
73 
74     // ICS (v 14) fixes a leak when using setTag(int, Object)
75     private static final boolean USE_TAG_ID = DataBinderMapper.TARGET_MIN_SDK >= 14;
76 
77     private static final boolean USE_CHOREOGRAPHER = SDK_INT >= 16;
78 
79     /**
80      * Method object extracted out to attach a listener to a bound Observable object.
81      */
82     private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
83         @Override
84         public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
85             return new WeakPropertyListener(viewDataBinding, localFieldId).getListener();
86         }
87     };
88 
89     /**
90      * Method object extracted out to attach a listener to a bound ObservableList object.
91      */
92     private static final CreateWeakListener CREATE_LIST_LISTENER = new CreateWeakListener() {
93         @Override
94         public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
95             return new WeakListListener(viewDataBinding, localFieldId).getListener();
96         }
97     };
98 
99     /**
100      * Method object extracted out to attach a listener to a bound ObservableMap object.
101      */
102     private static final CreateWeakListener CREATE_MAP_LISTENER = new CreateWeakListener() {
103         @Override
104         public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
105             return new WeakMapListener(viewDataBinding, localFieldId).getListener();
106         }
107     };
108 
109     private static final CallbackRegistry.NotifierCallback<OnRebindCallback, ViewDataBinding, Void>
110         REBIND_NOTIFIER = new NotifierCallback<OnRebindCallback, ViewDataBinding, Void>() {
111         @Override
112         public void onNotifyCallback(OnRebindCallback callback, ViewDataBinding sender, int mode,
113                 Void arg2) {
114             switch (mode) {
115                 case REBIND:
116                     if (!callback.onPreBind(sender)) {
117                         sender.mRebindHalted = true;
118                     }
119                     break;
120                 case HALTED:
121                     callback.onCanceled(sender);
122                     break;
123                 case REBOUND:
124                     callback.onBound(sender);
125                     break;
126             }
127         }
128     };
129 
130     private static final OnAttachStateChangeListener ROOT_REATTACHED_LISTENER;
131 
132     static {
133         if (VERSION.SDK_INT < VERSION_CODES.KITKAT) {
134             ROOT_REATTACHED_LISTENER = null;
135         } else {
136             ROOT_REATTACHED_LISTENER = new OnAttachStateChangeListener() {
137                 @TargetApi(VERSION_CODES.KITKAT)
138                 @Override
139                 public void onViewAttachedToWindow(View v) {
140                     // execute the pending bindings.
141                     final ViewDataBinding binding = getBinding(v);
142                     binding.mRebindRunnable.run();
143                     v.removeOnAttachStateChangeListener(this);
144                 }
145 
146                 @Override
147                 public void onViewDetachedFromWindow(View v) {
148                 }
149             };
150         }
151     }
152 
153     /**
154      * Runnable executed on animation heartbeat to rebind the dirty Views.
155      */
156     private final Runnable mRebindRunnable = new Runnable() {
157         @Override
158         public void run() {
159             synchronized (this) {
160                 mPendingRebind = false;
161             }
162             if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
163                 // Nested so that we don't get a lint warning in IntelliJ
164                 if (!mRoot.isAttachedToWindow()) {
165                     // Don't execute the pending bindings until the View
166                     // is attached again.
167                     mRoot.removeOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
168                     mRoot.addOnAttachStateChangeListener(ROOT_REATTACHED_LISTENER);
169                     return;
170                 }
171             }
172             executePendingBindings();
173         }
174     };
175 
176     /**
177      * Flag indicates that there are pending bindings that need to be reevaluated.
178      */
179     private boolean mPendingRebind = false;
180 
181     /**
182      * Indicates that a onPreBind has stopped the executePendingBindings call.
183      */
184     private boolean mRebindHalted = false;
185 
186     /**
187      * The observed expressions.
188      */
189     private WeakListener[] mLocalFieldObservers;
190 
191     /**
192      * The root View that this Binding is associated with.
193      */
194     private final View mRoot;
195 
196     /**
197      * The collection of OnRebindCallbacks.
198      */
199     private CallbackRegistry<OnRebindCallback, ViewDataBinding, Void> mRebindCallbacks;
200 
201     /**
202      * Flag to prevent reentrant executePendingBinding calls.
203      */
204     private boolean mIsExecutingPendingBindings;
205 
206     // null api < 16
207     private Choreographer mChoreographer;
208 
209     private final Choreographer.FrameCallback mFrameCallback;
210 
211     // null api >= 16
212     private Handler mUIThreadHandler;
213 
214     /**
215      * The DataBindingComponent used by this data binding. This is used for BindingAdapters
216      * that are instance methods to retrieve the class instance that implements the
217      * adapter.
218      *
219      * @hide
220      */
221     protected final DataBindingComponent mBindingComponent;
222 
223     /**
224      * @hide
225      */
ViewDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount)226     protected ViewDataBinding(DataBindingComponent bindingComponent, View root, int localFieldCount) {
227         mBindingComponent = bindingComponent;
228         mLocalFieldObservers = new WeakListener[localFieldCount];
229         this.mRoot = root;
230         if (Looper.myLooper() == null) {
231             throw new IllegalStateException("DataBinding must be created in view's UI Thread");
232         }
233         if (USE_CHOREOGRAPHER) {
234             mChoreographer = Choreographer.getInstance();
235             mFrameCallback = new Choreographer.FrameCallback() {
236                 @Override
237                 public void doFrame(long frameTimeNanos) {
238                     mRebindRunnable.run();
239                 }
240             };
241         } else {
242             mFrameCallback = null;
243             mUIThreadHandler = new Handler(Looper.myLooper());
244         }
245     }
246 
247     /**
248      * @hide
249      */
setRootTag(View view)250     protected void setRootTag(View view) {
251         if (USE_TAG_ID) {
252             view.setTag(R.id.dataBinding, this);
253         } else {
254             view.setTag(this);
255         }
256     }
257 
258     /**
259      * @hide
260      */
setRootTag(View[] views)261     protected void setRootTag(View[] views) {
262         if (USE_TAG_ID) {
263             for (View view : views) {
264                 view.setTag(R.id.dataBinding, this);
265             }
266         } else {
267             for (View view : views) {
268                 view.setTag(this);
269             }
270         }
271     }
272 
273     /**
274      * @hide
275      */
getBuildSdkInt()276     public static int getBuildSdkInt() {
277         return SDK_INT;
278     }
279 
280     /**
281      * Called when an observed object changes. Sets the appropriate dirty flag if applicable.
282      * @param localFieldId The index into mLocalFieldObservers that this Object resides in.
283      * @param object The object that has changed.
284      * @param fieldId The BR ID of the field being changed or _all if
285      *                no specific field is being notified.
286      * @return true if this change should cause a change to the UI.
287      * @hide
288      */
onFieldChange(int localFieldId, Object object, int fieldId)289     protected abstract boolean onFieldChange(int localFieldId, Object object, int fieldId);
290 
291     /**
292      * Set a value value in the Binding class.
293      * <p>
294      * Typically, the developer will be able to call the subclass's set method directly. For
295      * example, if there is a variable <code>x</code> in the Binding, a <code>setX</code> method
296      * will be generated. However, there are times when the specific subclass of ViewDataBinding
297      * is unknown, so the generated method cannot be discovered without reflection. The
298      * setVariable call allows the values of variables to be set without reflection.
299      *
300      * @param variableId the BR id of the variable to be set. For example, if the variable is
301      *                   <code>x</code>, then variableId will be <code>BR.x</code>.
302      * @param value The new value of the variable to be set.
303      * @return <code>true</code> if the variable is declared or used in the binding or
304      * <code>false</code> otherwise.
305      */
setVariable(int variableId, Object value)306     public abstract boolean setVariable(int variableId, Object value);
307 
308     /**
309      * Add a listener to be called when reevaluating dirty fields. This also allows automatic
310      * updates to be halted, but does not stop explicit calls to {@link #executePendingBindings()}.
311      *
312      * @param listener The listener to add.
313      */
addOnRebindCallback(OnRebindCallback listener)314     public void addOnRebindCallback(OnRebindCallback listener) {
315         if (mRebindCallbacks == null) {
316             mRebindCallbacks = new CallbackRegistry<OnRebindCallback, ViewDataBinding, Void>(REBIND_NOTIFIER);
317         }
318         mRebindCallbacks.add(listener);
319     }
320 
321     /**
322      * Removes a listener that was added in {@link #addOnRebindCallback(OnRebindCallback)}.
323      *
324      * @param listener The listener to remove.
325      */
removeOnRebindCallback(OnRebindCallback listener)326     public void removeOnRebindCallback(OnRebindCallback listener) {
327         if (mRebindCallbacks != null) {
328             mRebindCallbacks.remove(listener);
329         }
330     }
331 
332     /**
333      * Evaluates the pending bindings, updating any Views that have expressions bound to
334      * modified variables. This <b>must</b> be run on the UI thread.
335      */
executePendingBindings()336     public void executePendingBindings() {
337         if (mIsExecutingPendingBindings) {
338             requestRebind();
339             return;
340         }
341         if (!hasPendingBindings()) {
342             return;
343         }
344         mIsExecutingPendingBindings = true;
345         mRebindHalted = false;
346         if (mRebindCallbacks != null) {
347             mRebindCallbacks.notifyCallbacks(this, REBIND, null);
348 
349             // The onRebindListeners will change mPendingHalted
350             if (mRebindHalted) {
351                 mRebindCallbacks.notifyCallbacks(this, HALTED, null);
352             }
353         }
354         if (!mRebindHalted) {
355             executeBindings();
356             if (mRebindCallbacks != null) {
357                 mRebindCallbacks.notifyCallbacks(this, REBOUND, null);
358             }
359         }
360         mIsExecutingPendingBindings = false;
361     }
362 
forceExecuteBindings()363     void forceExecuteBindings() {
364         executeBindings();
365     }
366 
367     /**
368      * @hide
369      */
executeBindings()370     protected abstract void executeBindings();
371 
372     /**
373      * Invalidates all binding expressions and requests a new rebind to refresh UI.
374      */
invalidateAll()375     public abstract void invalidateAll();
376 
377     /**
378      * Returns whether the UI needs to be refresh to represent the current data.
379      *
380      * @return true if any field has changed and the binding should be evaluated.
381      */
hasPendingBindings()382     public abstract boolean hasPendingBindings();
383 
384     /**
385      * Removes binding listeners to expression variables.
386      */
unbind()387     public void unbind() {
388         for (WeakListener weakListener : mLocalFieldObservers) {
389             if (weakListener != null) {
390                 weakListener.unregister();
391             }
392         }
393     }
394 
395     @Override
finalize()396     protected void finalize() throws Throwable {
397         unbind();
398     }
399 
getBinding(View v)400     static ViewDataBinding getBinding(View v) {
401         if (v != null) {
402             if (USE_TAG_ID) {
403                 return (ViewDataBinding) v.getTag(R.id.dataBinding);
404             } else {
405                 final Object tag = v.getTag();
406                 if (tag instanceof ViewDataBinding) {
407                     return (ViewDataBinding) tag;
408                 }
409             }
410         }
411         return null;
412     }
413 
414     /**
415      * Returns the outermost View in the layout file associated with the Binding. If this
416      * binding is for a merge layout file, this will return the first root in the merge tag.
417      *
418      * @return the outermost View in the layout file associated with the Binding.
419      */
getRoot()420     public View getRoot() {
421         return mRoot;
422     }
423 
handleFieldChange(int mLocalFieldId, Object object, int fieldId)424     private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
425         boolean result = onFieldChange(mLocalFieldId, object, fieldId);
426         if (result) {
427             requestRebind();
428         }
429     }
430 
431     /**
432      * @hide
433      */
unregisterFrom(int localFieldId)434     protected boolean unregisterFrom(int localFieldId) {
435         WeakListener listener = mLocalFieldObservers[localFieldId];
436         if (listener != null) {
437             return listener.unregister();
438         }
439         return false;
440     }
441 
442     /**
443      * @hide
444      */
requestRebind()445     protected void requestRebind() {
446         synchronized (this) {
447             if (mPendingRebind) {
448                 return;
449             }
450             mPendingRebind = true;
451         }
452         if (USE_CHOREOGRAPHER) {
453             mChoreographer.postFrameCallback(mFrameCallback);
454         } else {
455             mUIThreadHandler.post(mRebindRunnable);
456         }
457 
458     }
459 
460     /**
461      * @hide
462      */
getObservedField(int localFieldId)463     protected Object getObservedField(int localFieldId) {
464         WeakListener listener = mLocalFieldObservers[localFieldId];
465         if (listener == null) {
466             return null;
467         }
468         return listener.getTarget();
469     }
470 
updateRegistration(int localFieldId, Object observable, CreateWeakListener listenerCreator)471     private boolean updateRegistration(int localFieldId, Object observable,
472             CreateWeakListener listenerCreator) {
473         if (observable == null) {
474             return unregisterFrom(localFieldId);
475         }
476         WeakListener listener = mLocalFieldObservers[localFieldId];
477         if (listener == null) {
478             registerTo(localFieldId, observable, listenerCreator);
479             return true;
480         }
481         if (listener.getTarget() == observable) {
482             return false;//nothing to do, same object
483         }
484         unregisterFrom(localFieldId);
485         registerTo(localFieldId, observable, listenerCreator);
486         return true;
487     }
488 
489     /**
490      * @hide
491      */
updateRegistration(int localFieldId, Observable observable)492     protected boolean updateRegistration(int localFieldId, Observable observable) {
493         return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
494     }
495 
496     /**
497      * @hide
498      */
updateRegistration(int localFieldId, ObservableList observable)499     protected boolean updateRegistration(int localFieldId, ObservableList observable) {
500         return updateRegistration(localFieldId, observable, CREATE_LIST_LISTENER);
501     }
502 
503     /**
504      * @hide
505      */
updateRegistration(int localFieldId, ObservableMap observable)506     protected boolean updateRegistration(int localFieldId, ObservableMap observable) {
507         return updateRegistration(localFieldId, observable, CREATE_MAP_LISTENER);
508     }
509 
510     /**
511      * @hide
512      */
ensureBindingComponentIsNotNull(Class<?> oneExample)513     protected void ensureBindingComponentIsNotNull(Class<?> oneExample) {
514         if (mBindingComponent == null) {
515             String errorMessage = "Required DataBindingComponent is null in class " +
516                     getClass().getSimpleName() + ". A BindingAdapter in " +
517                     oneExample.getCanonicalName() +
518                     " is not static and requires an object to use, retrieved from the " +
519                     "DataBindingComponent. If you don't use an inflation method taking a " +
520                     "DataBindingComponent, use DataBindingUtil.setDefaultComponent or " +
521                     "make all BindingAdapter methods static.";
522             throw new IllegalStateException(errorMessage);
523         }
524     }
525 
526     /**
527      * @hide
528      */
registerTo(int localFieldId, Object observable, CreateWeakListener listenerCreator)529     protected void registerTo(int localFieldId, Object observable,
530             CreateWeakListener listenerCreator) {
531         if (observable == null) {
532             return;
533         }
534         WeakListener listener = mLocalFieldObservers[localFieldId];
535         if (listener == null) {
536             listener = listenerCreator.create(this, localFieldId);
537             mLocalFieldObservers[localFieldId] = listener;
538         }
539         listener.setTarget(observable);
540     }
541 
542     /**
543      * @hide
544      */
bind(DataBindingComponent bindingComponent, View view, int layoutId)545     protected static ViewDataBinding bind(DataBindingComponent bindingComponent, View view,
546             int layoutId) {
547         return DataBindingUtil.bind(bindingComponent, view, layoutId);
548     }
549 
550     /**
551      * Walks the view hierarchy under root and pulls out tagged Views, includes, and views with
552      * IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find
553      * all bound and ID'd views.
554      *
555      * @param bindingComponent The binding component to use with this binding.
556      * @param root The root of the view hierarchy to walk.
557      * @param numBindings The total number of ID'd views, views with expressions, and includes
558      * @param includes The include layout information, indexed by their container's index.
559      * @param viewsWithIds Indexes of views that don't have tags, but have IDs.
560      * @return An array of size numBindings containing all Views in the hierarchy that have IDs
561      * (with elements in viewsWithIds), are tagged containing expressions, or the bindings for
562      * included layouts.
563      * @hide
564      */
mapBindings(DataBindingComponent bindingComponent, View root, int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds)565     protected static Object[] mapBindings(DataBindingComponent bindingComponent, View root,
566             int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {
567         Object[] bindings = new Object[numBindings];
568         mapBindings(bindingComponent, root, bindings, includes, viewsWithIds, true);
569         return bindings;
570     }
571 
572     /** @hide */
parse(String str, boolean fallback)573     protected static boolean parse(String str, boolean fallback) {
574         if (str == null) {
575             return fallback;
576         }
577         return Boolean.parseBoolean(str);
578     }
579 
580     /** @hide */
parse(String str, byte fallback)581     protected static byte parse(String str, byte fallback) {
582         try {
583             return Byte.parseByte(str);
584         } catch (NumberFormatException e) {
585             return fallback;
586         }
587     }
588 
589     /** @hide */
parse(String str, short fallback)590     protected static short parse(String str, short fallback) {
591         try {
592             return Short.parseShort(str);
593         } catch (NumberFormatException e) {
594             return fallback;
595         }
596     }
597 
598     /** @hide */
parse(String str, int fallback)599     protected static int parse(String str, int fallback) {
600         try {
601             return Integer.parseInt(str);
602         } catch (NumberFormatException e) {
603             return fallback;
604         }
605     }
606 
607     /** @hide */
parse(String str, long fallback)608     protected static long parse(String str, long fallback) {
609         try {
610             return Long.parseLong(str);
611         } catch (NumberFormatException e) {
612             return fallback;
613         }
614     }
615 
616     /** @hide */
parse(String str, float fallback)617     protected static float parse(String str, float fallback) {
618         try {
619             return Float.parseFloat(str);
620         } catch (NumberFormatException e) {
621             return fallback;
622         }
623     }
624 
625     /** @hide */
parse(String str, double fallback)626     protected static double parse(String str, double fallback) {
627         try {
628             return Double.parseDouble(str);
629         } catch (NumberFormatException e) {
630             return fallback;
631         }
632     }
633 
634     /** @hide */
parse(String str, char fallback)635     protected static char parse(String str, char fallback) {
636         if (str == null || str.isEmpty()) {
637             return fallback;
638         }
639         return str.charAt(0);
640     }
641 
642     /** @hide */
getColorFromResource(View view, int resourceId)643     protected static int getColorFromResource(View view, int resourceId) {
644         if (VERSION.SDK_INT >= VERSION_CODES.M) {
645             return view.getContext().getColor(resourceId);
646         } else {
647             return view.getResources().getColor(resourceId);
648         }
649     }
650 
651     /** @hide */
getColorStateListFromResource(View view, int resourceId)652     protected static ColorStateList getColorStateListFromResource(View view, int resourceId) {
653         if (VERSION.SDK_INT >= VERSION_CODES.M) {
654             return view.getContext().getColorStateList(resourceId);
655         } else {
656             return view.getResources().getColorStateList(resourceId);
657         }
658     }
659 
660     /** @hide */
getDrawableFromResource(View view, int resourceId)661     protected static Drawable getDrawableFromResource(View view, int resourceId) {
662         if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
663             return view.getContext().getDrawable(resourceId);
664         } else {
665             return view.getResources().getDrawable(resourceId);
666         }
667     }
668 
669     /** @hide */
getFromArray(T[] arr, int index)670     protected static <T> T getFromArray(T[] arr, int index) {
671         if (arr == null || index < 0 || index >= arr.length) {
672             return null;
673         }
674         return arr[index];
675     }
676 
677     /** @hide */
setTo(T[] arr, int index, T value)678     protected static <T> void setTo(T[] arr, int index, T value) {
679         if (arr == null || index < 0 || index >= arr.length) {
680             return;
681         }
682         arr[index] = value;
683     }
684 
685     /** @hide */
getFromArray(boolean[] arr, int index)686     protected static boolean getFromArray(boolean[] arr, int index) {
687         if (arr == null || index < 0 || index >= arr.length) {
688             return false;
689         }
690         return arr[index];
691     }
692 
693     /** @hide */
setTo(boolean[] arr, int index, boolean value)694     protected static void setTo(boolean[] arr, int index, boolean value) {
695         if (arr == null || index < 0 || index >= arr.length) {
696             return;
697         }
698         arr[index] = value;
699     }
700 
701     /** @hide */
getFromArray(byte[] arr, int index)702     protected static byte getFromArray(byte[] arr, int index) {
703         if (arr == null || index < 0 || index >= arr.length) {
704             return 0;
705         }
706         return arr[index];
707     }
708 
709     /** @hide */
setTo(byte[] arr, int index, byte value)710     protected static void setTo(byte[] arr, int index, byte value) {
711         if (arr == null || index < 0 || index >= arr.length) {
712             return;
713         }
714         arr[index] = value;
715     }
716 
717     /** @hide */
getFromArray(short[] arr, int index)718     protected static short getFromArray(short[] arr, int index) {
719         if (arr == null || index < 0 || index >= arr.length) {
720             return 0;
721         }
722         return arr[index];
723     }
724 
725     /** @hide */
setTo(short[] arr, int index, short value)726     protected static void setTo(short[] arr, int index, short value) {
727         if (arr == null || index < 0 || index >= arr.length) {
728             return;
729         }
730         arr[index] = value;
731     }
732 
733     /** @hide */
getFromArray(char[] arr, int index)734     protected static char getFromArray(char[] arr, int index) {
735         if (arr == null || index < 0 || index >= arr.length) {
736             return 0;
737         }
738         return arr[index];
739     }
740 
741     /** @hide */
setTo(char[] arr, int index, char value)742     protected static void setTo(char[] arr, int index, char value) {
743         if (arr == null || index < 0 || index >= arr.length) {
744             return;
745         }
746         arr[index] = value;
747     }
748 
749     /** @hide */
getFromArray(int[] arr, int index)750     protected static int getFromArray(int[] arr, int index) {
751         if (arr == null || index < 0 || index >= arr.length) {
752             return 0;
753         }
754         return arr[index];
755     }
756 
757     /** @hide */
setTo(int[] arr, int index, int value)758     protected static void setTo(int[] arr, int index, int value) {
759         if (arr == null || index < 0 || index >= arr.length) {
760             return;
761         }
762         arr[index] = value;
763     }
764 
765     /** @hide */
getFromArray(long[] arr, int index)766     protected static long getFromArray(long[] arr, int index) {
767         if (arr == null || index < 0 || index >= arr.length) {
768             return 0;
769         }
770         return arr[index];
771     }
772 
773     /** @hide */
setTo(long[] arr, int index, long value)774     protected static void setTo(long[] arr, int index, long value) {
775         if (arr == null || index < 0 || index >= arr.length) {
776             return;
777         }
778         arr[index] = value;
779     }
780 
781     /** @hide */
getFromArray(float[] arr, int index)782     protected static float getFromArray(float[] arr, int index) {
783         if (arr == null || index < 0 || index >= arr.length) {
784             return 0;
785         }
786         return arr[index];
787     }
788 
789     /** @hide */
setTo(float[] arr, int index, float value)790     protected static void setTo(float[] arr, int index, float value) {
791         if (arr == null || index < 0 || index >= arr.length) {
792             return;
793         }
794         arr[index] = value;
795     }
796 
797     /** @hide */
getFromArray(double[] arr, int index)798     protected static double getFromArray(double[] arr, int index) {
799         if (arr == null || index < 0 || index >= arr.length) {
800             return 0;
801         }
802         return arr[index];
803     }
804 
805     /** @hide */
setTo(double[] arr, int index, double value)806     protected static void setTo(double[] arr, int index, double value) {
807         if (arr == null || index < 0 || index >= arr.length) {
808             return;
809         }
810         arr[index] = value;
811     }
812 
813     /** @hide */
getFromList(List<T> list, int index)814     protected static <T> T getFromList(List<T> list, int index) {
815         if (list == null || index < 0 || index >= list.size()) {
816             return null;
817         }
818         return list.get(index);
819     }
820 
821     /** @hide */
setTo(List<T> list, int index, T value)822     protected static <T> void setTo(List<T> list, int index, T value) {
823         if (list == null || index < 0 || index >= list.size()) {
824             return;
825         }
826         list.set(index, value);
827     }
828 
829     /** @hide */
getFromList(SparseArray<T> list, int index)830     protected static <T> T getFromList(SparseArray<T> list, int index) {
831         if (list == null || index < 0) {
832             return null;
833         }
834         return list.get(index);
835     }
836 
837     /** @hide */
setTo(SparseArray<T> list, int index, T value)838     protected static <T> void setTo(SparseArray<T> list, int index, T value) {
839         if (list == null || index < 0 || index >= list.size()) {
840             return;
841         }
842         list.put(index, value);
843     }
844 
845     /** @hide */
846     @TargetApi(VERSION_CODES.JELLY_BEAN)
getFromList(LongSparseArray<T> list, int index)847     protected static <T> T getFromList(LongSparseArray<T> list, int index) {
848         if (list == null || index < 0) {
849             return null;
850         }
851         return list.get(index);
852     }
853 
854     /** @hide */
855     @TargetApi(VERSION_CODES.JELLY_BEAN)
setTo(LongSparseArray<T> list, int index, T value)856     protected static <T> void setTo(LongSparseArray<T> list, int index, T value) {
857         if (list == null || index < 0 || index >= list.size()) {
858             return;
859         }
860         list.put(index, value);
861     }
862 
863     /** @hide */
getFromList(android.support.v4.util.LongSparseArray<T> list, int index)864     protected static <T> T getFromList(android.support.v4.util.LongSparseArray<T> list, int index) {
865         if (list == null || index < 0) {
866             return null;
867         }
868         return list.get(index);
869     }
870 
871     /** @hide */
setTo(android.support.v4.util.LongSparseArray<T> list, int index, T value)872     protected static <T> void setTo(android.support.v4.util.LongSparseArray<T> list, int index,
873             T value) {
874         if (list == null || index < 0 || index >= list.size()) {
875             return;
876         }
877         list.put(index, value);
878     }
879 
880     /** @hide */
getFromList(SparseBooleanArray list, int index)881     protected static boolean getFromList(SparseBooleanArray list, int index) {
882         if (list == null || index < 0) {
883             return false;
884         }
885         return list.get(index);
886     }
887 
888     /** @hide */
setTo(SparseBooleanArray list, int index, boolean value)889     protected static void setTo(SparseBooleanArray list, int index, boolean value) {
890         if (list == null || index < 0 || index >= list.size()) {
891             return;
892         }
893         list.put(index, value);
894     }
895 
896     /** @hide */
getFromList(SparseIntArray list, int index)897     protected static int getFromList(SparseIntArray list, int index) {
898         if (list == null || index < 0) {
899             return 0;
900         }
901         return list.get(index);
902     }
903 
904     /** @hide */
setTo(SparseIntArray list, int index, int value)905     protected static void setTo(SparseIntArray list, int index, int value) {
906         if (list == null || index < 0 || index >= list.size()) {
907             return;
908         }
909         list.put(index, value);
910     }
911 
912     /** @hide */
913     @TargetApi(VERSION_CODES.JELLY_BEAN_MR2)
getFromList(SparseLongArray list, int index)914     protected static long getFromList(SparseLongArray list, int index) {
915         if (list == null || index < 0) {
916             return 0;
917         }
918         return list.get(index);
919     }
920 
921     /** @hide */
922     @TargetApi(VERSION_CODES.JELLY_BEAN_MR2)
setTo(SparseLongArray list, int index, long value)923     protected static void setTo(SparseLongArray list, int index, long value) {
924         if (list == null || index < 0 || index >= list.size()) {
925             return;
926         }
927         list.put(index, value);
928     }
929 
930     /** @hide */
getFrom(Map<K, T> map, K key)931     protected static <K, T> T getFrom(Map<K, T> map, K key) {
932         if (map == null) {
933             return null;
934         }
935         return map.get(key);
936     }
937 
938     /** @hide */
setTo(Map<K, T> map, K key, T value)939     protected static <K, T> void setTo(Map<K, T> map, K key, T value) {
940         if (map == null) {
941             return;
942         }
943         map.put(key, value);
944     }
945 
946     /** @hide */
setBindingInverseListener(ViewDataBinding binder, InverseBindingListener oldListener, PropertyChangedInverseListener listener)947     protected static void setBindingInverseListener(ViewDataBinding binder,
948             InverseBindingListener oldListener, PropertyChangedInverseListener listener) {
949         if (oldListener != listener) {
950             if (oldListener != null) {
951                 binder.removeOnPropertyChangedCallback(
952                         (PropertyChangedInverseListener) oldListener);
953             }
954             if (listener != null) {
955                 binder.addOnPropertyChangedCallback(listener);
956             }
957         }
958     }
959 
960     /**
961      * Walks the view hierarchy under roots and pulls out tagged Views, includes, and views with
962      * IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find
963      * all bound and ID'd views.
964      *
965      * @param bindingComponent The binding component to use with this binding.
966      * @param roots The root Views of the view hierarchy to walk. This is used with merge tags.
967      * @param numBindings The total number of ID'd views, views with expressions, and includes
968      * @param includes The include layout information, indexed by their container's index.
969      * @param viewsWithIds Indexes of views that don't have tags, but have IDs.
970      * @return An array of size numBindings containing all Views in the hierarchy that have IDs
971      * (with elements in viewsWithIds), are tagged containing expressions, or the bindings for
972      * included layouts.
973      * @hide
974      */
mapBindings(DataBindingComponent bindingComponent, View[] roots, int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds)975     protected static Object[] mapBindings(DataBindingComponent bindingComponent, View[] roots,
976             int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {
977         Object[] bindings = new Object[numBindings];
978         for (int i = 0; i < roots.length; i++) {
979             mapBindings(bindingComponent, roots[i], bindings, includes, viewsWithIds, true);
980         }
981         return bindings;
982     }
983 
mapBindings(DataBindingComponent bindingComponent, View view, Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds, boolean isRoot)984     private static void mapBindings(DataBindingComponent bindingComponent, View view,
985             Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
986             boolean isRoot) {
987         final int indexInIncludes;
988         final ViewDataBinding existingBinding = getBinding(view);
989         if (existingBinding != null) {
990             return;
991         }
992         Object objTag = view.getTag();
993         final String tag = (objTag instanceof String) ? (String) objTag : null;
994         boolean isBound = false;
995         if (isRoot && tag != null && tag.startsWith("layout")) {
996             final int underscoreIndex = tag.lastIndexOf('_');
997             if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) {
998                 final int index = parseTagInt(tag, underscoreIndex + 1);
999                 if (bindings[index] == null) {
1000                     bindings[index] = view;
1001                 }
1002                 indexInIncludes = includes == null ? -1 : index;
1003                 isBound = true;
1004             } else {
1005                 indexInIncludes = -1;
1006             }
1007         } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
1008             int tagIndex = parseTagInt(tag, BINDING_NUMBER_START);
1009             if (bindings[tagIndex] == null) {
1010                 bindings[tagIndex] = view;
1011             }
1012             isBound = true;
1013             indexInIncludes = includes == null ? -1 : tagIndex;
1014         } else {
1015             // Not a bound view
1016             indexInIncludes = -1;
1017         }
1018         if (!isBound) {
1019             final int id = view.getId();
1020             if (id > 0) {
1021                 int index;
1022                 if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0 &&
1023                         bindings[index] == null) {
1024                     bindings[index] = view;
1025                 }
1026             }
1027         }
1028 
1029         if (view instanceof  ViewGroup) {
1030             final ViewGroup viewGroup = (ViewGroup) view;
1031             final int count = viewGroup.getChildCount();
1032             int minInclude = 0;
1033             for (int i = 0; i < count; i++) {
1034                 final View child = viewGroup.getChildAt(i);
1035                 boolean isInclude = false;
1036                 if (indexInIncludes >= 0 && child.getTag() instanceof String) {
1037                     String childTag = (String) child.getTag();
1038                     if (childTag.endsWith("_0") &&
1039                             childTag.startsWith("layout") && childTag.indexOf('/') > 0) {
1040                         // This *could* be an include. Test against the expected includes.
1041                         int includeIndex = findIncludeIndex(childTag, minInclude,
1042                                 includes, indexInIncludes);
1043                         if (includeIndex >= 0) {
1044                             isInclude = true;
1045                             minInclude = includeIndex + 1;
1046                             final int index = includes.indexes[indexInIncludes][includeIndex];
1047                             final int layoutId = includes.layoutIds[indexInIncludes][includeIndex];
1048                             int lastMatchingIndex = findLastMatching(viewGroup, i);
1049                             if (lastMatchingIndex == i) {
1050                                 bindings[index] = DataBindingUtil.bind(bindingComponent, child,
1051                                         layoutId);
1052                             } else {
1053                                 final int includeCount =  lastMatchingIndex - i + 1;
1054                                 final View[] included = new View[includeCount];
1055                                 for (int j = 0; j < includeCount; j++) {
1056                                     included[j] = viewGroup.getChildAt(i + j);
1057                                 }
1058                                 bindings[index] = DataBindingUtil.bind(bindingComponent, included,
1059                                         layoutId);
1060                                 i += includeCount - 1;
1061                             }
1062                         }
1063                     }
1064                 }
1065                 if (!isInclude) {
1066                     mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
1067                 }
1068             }
1069         }
1070     }
1071 
findIncludeIndex(String tag, int minInclude, IncludedLayouts included, int includedIndex)1072     private static int findIncludeIndex(String tag, int minInclude,
1073             IncludedLayouts included, int includedIndex) {
1074         final int slashIndex = tag.indexOf('/');
1075         final CharSequence layoutName = tag.subSequence(slashIndex + 1, tag.length() - 2);
1076 
1077         final String[] layouts = included.layouts[includedIndex];
1078         final int length = layouts.length;
1079         for (int i = minInclude; i < length; i++) {
1080             final String layout = layouts[i];
1081             if (TextUtils.equals(layoutName, layout)) {
1082                 return i;
1083             }
1084         }
1085         return -1;
1086     }
1087 
findLastMatching(ViewGroup viewGroup, int firstIncludedIndex)1088     private static int findLastMatching(ViewGroup viewGroup, int firstIncludedIndex) {
1089         final View firstView = viewGroup.getChildAt(firstIncludedIndex);
1090         final String firstViewTag = (String) firstView.getTag();
1091         final String tagBase = firstViewTag.substring(0, firstViewTag.length() - 1); // don't include the "0"
1092         final int tagSequenceIndex = tagBase.length();
1093 
1094         final int count = viewGroup.getChildCount();
1095         int max = firstIncludedIndex;
1096         for (int i = firstIncludedIndex + 1; i < count; i++) {
1097             final View view = viewGroup.getChildAt(i);
1098             final Object objTag = view.getTag();
1099             final String tag = objTag instanceof String ? (String) view.getTag() : null;
1100             if (tag != null && tag.startsWith(tagBase)) {
1101                 if (tag.length() == firstViewTag.length() && tag.charAt(tag.length() - 1) == '0') {
1102                     return max; // Found another instance of the include
1103                 }
1104                 if (isNumeric(tag, tagSequenceIndex)) {
1105                     max = i;
1106                 }
1107             }
1108         }
1109         return max;
1110     }
1111 
isNumeric(String tag, int startIndex)1112     private static boolean isNumeric(String tag, int startIndex) {
1113         int length = tag.length();
1114         if (length == startIndex) {
1115             return false; // no numerals
1116         }
1117         for (int i = startIndex; i < length; i++) {
1118             if (!Character.isDigit(tag.charAt(i))) {
1119                 return false;
1120             }
1121         }
1122         return true;
1123     }
1124 
1125     /**
1126      * Parse the tag without creating a new String object. This is fast and assumes the
1127      * tag is in the correct format.
1128      * @param str The tag string.
1129      * @return The binding tag number parsed from the tag string.
1130      */
parseTagInt(String str, int startIndex)1131     private static int parseTagInt(String str, int startIndex) {
1132         final int end = str.length();
1133         int val = 0;
1134         for (int i = startIndex; i < end; i++) {
1135             val *= 10;
1136             char c = str.charAt(i);
1137             val += (c - '0');
1138         }
1139         return val;
1140     }
1141 
1142     private interface ObservableReference<T> {
getListener()1143         WeakListener<T> getListener();
addListener(T target)1144         void addListener(T target);
removeListener(T target)1145         void removeListener(T target);
1146     }
1147 
1148     private static class WeakListener<T> extends WeakReference<ViewDataBinding> {
1149         private final ObservableReference<T> mObservable;
1150         protected final int mLocalFieldId;
1151         private T mTarget;
1152 
WeakListener(ViewDataBinding binder, int localFieldId, ObservableReference<T> observable)1153         public WeakListener(ViewDataBinding binder, int localFieldId,
1154                 ObservableReference<T> observable) {
1155             super(binder);
1156             mLocalFieldId = localFieldId;
1157             mObservable = observable;
1158         }
1159 
setTarget(T object)1160         public void setTarget(T object) {
1161             unregister();
1162             mTarget = object;
1163             if (mTarget != null) {
1164                 mObservable.addListener(mTarget);
1165             }
1166         }
1167 
unregister()1168         public boolean unregister() {
1169             boolean unregistered = false;
1170             if (mTarget != null) {
1171                 mObservable.removeListener(mTarget);
1172                 unregistered = true;
1173             }
1174             mTarget = null;
1175             return unregistered;
1176         }
1177 
getTarget()1178         public T getTarget() {
1179             return mTarget;
1180         }
1181 
getBinder()1182         protected ViewDataBinding getBinder() {
1183             ViewDataBinding binder = get();
1184             if (binder == null) {
1185                 unregister(); // The binder is dead
1186             }
1187             return binder;
1188         }
1189     }
1190 
1191     private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback
1192             implements ObservableReference<Observable> {
1193         final WeakListener<Observable> mListener;
1194 
WeakPropertyListener(ViewDataBinding binder, int localFieldId)1195         public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
1196             mListener = new WeakListener<Observable>(binder, localFieldId, this);
1197         }
1198 
1199         @Override
getListener()1200         public WeakListener<Observable> getListener() {
1201             return mListener;
1202         }
1203 
1204         @Override
addListener(Observable target)1205         public void addListener(Observable target) {
1206             target.addOnPropertyChangedCallback(this);
1207         }
1208 
1209         @Override
removeListener(Observable target)1210         public void removeListener(Observable target) {
1211             target.removeOnPropertyChangedCallback(this);
1212         }
1213 
1214         @Override
onPropertyChanged(Observable sender, int propertyId)1215         public void onPropertyChanged(Observable sender, int propertyId) {
1216             ViewDataBinding binder = mListener.getBinder();
1217             if (binder == null) {
1218                 return;
1219             }
1220             Observable obj = mListener.getTarget();
1221             if (obj != sender) {
1222                 return; // notification from the wrong object?
1223             }
1224             binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
1225         }
1226     }
1227 
1228     private static class WeakListListener extends ObservableList.OnListChangedCallback
1229             implements ObservableReference<ObservableList> {
1230         final WeakListener<ObservableList> mListener;
1231 
WeakListListener(ViewDataBinding binder, int localFieldId)1232         public WeakListListener(ViewDataBinding binder, int localFieldId) {
1233             mListener = new WeakListener<ObservableList>(binder, localFieldId, this);
1234         }
1235 
1236         @Override
getListener()1237         public WeakListener<ObservableList> getListener() {
1238             return mListener;
1239         }
1240 
1241         @Override
addListener(ObservableList target)1242         public void addListener(ObservableList target) {
1243             target.addOnListChangedCallback(this);
1244         }
1245 
1246         @Override
removeListener(ObservableList target)1247         public void removeListener(ObservableList target) {
1248             target.removeOnListChangedCallback(this);
1249         }
1250 
1251         @Override
onChanged(ObservableList sender)1252         public void onChanged(ObservableList sender) {
1253             ViewDataBinding binder = mListener.getBinder();
1254             if (binder == null) {
1255                 return;
1256             }
1257             ObservableList target = mListener.getTarget();
1258             if (target != sender) {
1259                 return; // We expect notifications only from sender
1260             }
1261             binder.handleFieldChange(mListener.mLocalFieldId, target, 0);
1262         }
1263 
1264         @Override
onItemRangeChanged(ObservableList sender, int positionStart, int itemCount)1265         public void onItemRangeChanged(ObservableList sender, int positionStart, int itemCount) {
1266             onChanged(sender);
1267         }
1268 
1269         @Override
onItemRangeInserted(ObservableList sender, int positionStart, int itemCount)1270         public void onItemRangeInserted(ObservableList sender, int positionStart, int itemCount) {
1271             onChanged(sender);
1272         }
1273 
1274         @Override
onItemRangeMoved(ObservableList sender, int fromPosition, int toPosition, int itemCount)1275         public void onItemRangeMoved(ObservableList sender, int fromPosition, int toPosition,
1276                 int itemCount) {
1277             onChanged(sender);
1278         }
1279 
1280         @Override
onItemRangeRemoved(ObservableList sender, int positionStart, int itemCount)1281         public void onItemRangeRemoved(ObservableList sender, int positionStart, int itemCount) {
1282             onChanged(sender);
1283         }
1284     }
1285 
1286     private static class WeakMapListener extends ObservableMap.OnMapChangedCallback
1287             implements ObservableReference<ObservableMap> {
1288         final WeakListener<ObservableMap> mListener;
1289 
WeakMapListener(ViewDataBinding binder, int localFieldId)1290         public WeakMapListener(ViewDataBinding binder, int localFieldId) {
1291             mListener = new WeakListener<ObservableMap>(binder, localFieldId, this);
1292         }
1293 
1294         @Override
getListener()1295         public WeakListener<ObservableMap> getListener() {
1296             return mListener;
1297         }
1298 
1299         @Override
addListener(ObservableMap target)1300         public void addListener(ObservableMap target) {
1301             target.addOnMapChangedCallback(this);
1302         }
1303 
1304         @Override
removeListener(ObservableMap target)1305         public void removeListener(ObservableMap target) {
1306             target.removeOnMapChangedCallback(this);
1307         }
1308 
1309         @Override
onMapChanged(ObservableMap sender, Object key)1310         public void onMapChanged(ObservableMap sender, Object key) {
1311             ViewDataBinding binder = mListener.getBinder();
1312             if (binder == null || sender != mListener.getTarget()) {
1313                 return;
1314             }
1315             binder.handleFieldChange(mListener.mLocalFieldId, sender, 0);
1316         }
1317     }
1318 
1319     private interface CreateWeakListener {
create(ViewDataBinding viewDataBinding, int localFieldId)1320         WeakListener create(ViewDataBinding viewDataBinding, int localFieldId);
1321     }
1322 
1323     /**
1324      * This class is used by generated subclasses of {@link ViewDataBinding} to track the
1325      * included layouts contained in the bound layout. This class is an implementation
1326      * detail of how binding expressions are mapped to Views after inflation.
1327      * @hide
1328      */
1329     protected static class IncludedLayouts {
1330         public final String[][] layouts;
1331         public final int[][] indexes;
1332         public final int[][] layoutIds;
1333 
IncludedLayouts(int bindingCount)1334         public IncludedLayouts(int bindingCount) {
1335             layouts = new String[bindingCount][];
1336             indexes = new int[bindingCount][];
1337             layoutIds = new int[bindingCount][];
1338         }
1339 
setIncludes(int index, String[] layouts, int[] indexes, int[] layoutIds)1340         public void setIncludes(int index, String[] layouts, int[] indexes, int[] layoutIds) {
1341             this.layouts[index] = layouts;
1342             this.indexes[index] = indexes;
1343             this.layoutIds[index] = layoutIds;
1344         }
1345     }
1346 
1347     /**
1348      * This class is used by generated subclasses of {@link ViewDataBinding} to listen for
1349      * changes on variables of Bindings. This is important for two-way data binding on variables
1350      * in included Bindings.
1351      * @hide
1352      */
1353     protected static abstract class PropertyChangedInverseListener
1354             extends Observable.OnPropertyChangedCallback implements InverseBindingListener {
1355         final int mPropertyId;
1356 
PropertyChangedInverseListener(int propertyId)1357         public PropertyChangedInverseListener(int propertyId) {
1358             mPropertyId = propertyId;
1359         }
1360 
1361         @Override
onPropertyChanged(Observable sender, int propertyId)1362         public void onPropertyChanged(Observable sender, int propertyId) {
1363             if (propertyId == mPropertyId || propertyId == 0) {
1364                 onChange();
1365             }
1366         }
1367     }
1368 }
1369