Home | History | Annotate | Download | only in databinding
      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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    363     void forceExecuteBindings() {
    364         executeBindings();
    365     }
    366 
    367     /**
    368      * @hide
    369      */
    370     protected abstract void executeBindings();
    371 
    372     /**
    373      * Invalidates all binding expressions and requests a new rebind to refresh UI.
    374      */
    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      */
    382     public abstract boolean hasPendingBindings();
    383 
    384     /**
    385      * Removes binding listeners to expression variables.
    386      */
    387     public void unbind() {
    388         for (WeakListener weakListener : mLocalFieldObservers) {
    389             if (weakListener != null) {
    390                 weakListener.unregister();
    391             }
    392         }
    393     }
    394 
    395     @Override
    396     protected void finalize() throws Throwable {
    397         unbind();
    398     }
    399 
    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      */
    420     public View getRoot() {
    421         return mRoot;
    422     }
    423 
    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      */
    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      */
    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      */
    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 
    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      */
    492     protected boolean updateRegistration(int localFieldId, Observable observable) {
    493         return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
    494     }
    495 
    496     /**
    497      * @hide
    498      */
    499     protected boolean updateRegistration(int localFieldId, ObservableList observable) {
    500         return updateRegistration(localFieldId, observable, CREATE_LIST_LISTENER);
    501     }
    502 
    503     /**
    504      * @hide
    505      */
    506     protected boolean updateRegistration(int localFieldId, ObservableMap observable) {
    507         return updateRegistration(localFieldId, observable, CREATE_MAP_LISTENER);
    508     }
    509 
    510     /**
    511      * @hide
    512      */
    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      */
    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      */
    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      */
    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 */
    573     protected int getColorFromResource(int resourceId) {
    574         if (VERSION.SDK_INT >= VERSION_CODES.M) {
    575             return getRoot().getContext().getColor(resourceId);
    576         } else {
    577             return getRoot().getResources().getColor(resourceId);
    578         }
    579     }
    580 
    581     /** @hide */
    582     protected ColorStateList getColorStateListFromResource(int resourceId) {
    583         if (VERSION.SDK_INT >= VERSION_CODES.M) {
    584             return getRoot().getContext().getColorStateList(resourceId);
    585         } else {
    586             return getRoot().getResources().getColorStateList(resourceId);
    587         }
    588     }
    589 
    590     /** @hide */
    591     protected Drawable getDrawableFromResource(int resourceId) {
    592         if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
    593             return getRoot().getContext().getDrawable(resourceId);
    594         } else {
    595             return getRoot().getResources().getDrawable(resourceId);
    596         }
    597     }
    598 
    599     /** @hide */
    600     protected static <T> T getFromArray(T[] arr, int index) {
    601         if (arr == null || index < 0 || index >= arr.length) {
    602             return null;
    603         }
    604         return arr[index];
    605     }
    606 
    607     /** @hide */
    608     protected static <T> void setTo(T[] arr, int index, T value) {
    609         if (arr == null || index < 0 || index >= arr.length) {
    610             return;
    611         }
    612         arr[index] = value;
    613     }
    614 
    615     /** @hide */
    616     protected static boolean getFromArray(boolean[] arr, int index) {
    617         if (arr == null || index < 0 || index >= arr.length) {
    618             return false;
    619         }
    620         return arr[index];
    621     }
    622 
    623     /** @hide */
    624     protected static void setTo(boolean[] arr, int index, boolean value) {
    625         if (arr == null || index < 0 || index >= arr.length) {
    626             return;
    627         }
    628         arr[index] = value;
    629     }
    630 
    631     /** @hide */
    632     protected static byte getFromArray(byte[] arr, int index) {
    633         if (arr == null || index < 0 || index >= arr.length) {
    634             return 0;
    635         }
    636         return arr[index];
    637     }
    638 
    639     /** @hide */
    640     protected static void setTo(byte[] arr, int index, byte value) {
    641         if (arr == null || index < 0 || index >= arr.length) {
    642             return;
    643         }
    644         arr[index] = value;
    645     }
    646 
    647     /** @hide */
    648     protected static short getFromArray(short[] arr, int index) {
    649         if (arr == null || index < 0 || index >= arr.length) {
    650             return 0;
    651         }
    652         return arr[index];
    653     }
    654 
    655     /** @hide */
    656     protected static void setTo(short[] arr, int index, short value) {
    657         if (arr == null || index < 0 || index >= arr.length) {
    658             return;
    659         }
    660         arr[index] = value;
    661     }
    662 
    663     /** @hide */
    664     protected static char getFromArray(char[] arr, int index) {
    665         if (arr == null || index < 0 || index >= arr.length) {
    666             return 0;
    667         }
    668         return arr[index];
    669     }
    670 
    671     /** @hide */
    672     protected static void setTo(char[] arr, int index, char value) {
    673         if (arr == null || index < 0 || index >= arr.length) {
    674             return;
    675         }
    676         arr[index] = value;
    677     }
    678 
    679     /** @hide */
    680     protected static int getFromArray(int[] arr, int index) {
    681         if (arr == null || index < 0 || index >= arr.length) {
    682             return 0;
    683         }
    684         return arr[index];
    685     }
    686 
    687     /** @hide */
    688     protected static void setTo(int[] arr, int index, int value) {
    689         if (arr == null || index < 0 || index >= arr.length) {
    690             return;
    691         }
    692         arr[index] = value;
    693     }
    694 
    695     /** @hide */
    696     protected static long getFromArray(long[] arr, int index) {
    697         if (arr == null || index < 0 || index >= arr.length) {
    698             return 0;
    699         }
    700         return arr[index];
    701     }
    702 
    703     /** @hide */
    704     protected static void setTo(long[] arr, int index, long value) {
    705         if (arr == null || index < 0 || index >= arr.length) {
    706             return;
    707         }
    708         arr[index] = value;
    709     }
    710 
    711     /** @hide */
    712     protected static float getFromArray(float[] arr, int index) {
    713         if (arr == null || index < 0 || index >= arr.length) {
    714             return 0;
    715         }
    716         return arr[index];
    717     }
    718 
    719     /** @hide */
    720     protected static void setTo(float[] arr, int index, float value) {
    721         if (arr == null || index < 0 || index >= arr.length) {
    722             return;
    723         }
    724         arr[index] = value;
    725     }
    726 
    727     /** @hide */
    728     protected static double getFromArray(double[] arr, int index) {
    729         if (arr == null || index < 0 || index >= arr.length) {
    730             return 0;
    731         }
    732         return arr[index];
    733     }
    734 
    735     /** @hide */
    736     protected static void setTo(double[] arr, int index, double value) {
    737         if (arr == null || index < 0 || index >= arr.length) {
    738             return;
    739         }
    740         arr[index] = value;
    741     }
    742 
    743     /** @hide */
    744     protected static <T> T getFromList(List<T> list, int index) {
    745         if (list == null || index < 0 || index >= list.size()) {
    746             return null;
    747         }
    748         return list.get(index);
    749     }
    750 
    751     /** @hide */
    752     protected static <T> void setTo(List<T> list, int index, T value) {
    753         if (list == null || index < 0 || index >= list.size()) {
    754             return;
    755         }
    756         list.set(index, value);
    757     }
    758 
    759     /** @hide */
    760     protected static <T> T getFromList(SparseArray<T> list, int index) {
    761         if (list == null || index < 0) {
    762             return null;
    763         }
    764         return list.get(index);
    765     }
    766 
    767     /** @hide */
    768     protected static <T> void setTo(SparseArray<T> list, int index, T value) {
    769         if (list == null || index < 0 || index >= list.size()) {
    770             return;
    771         }
    772         list.put(index, value);
    773     }
    774 
    775     /** @hide */
    776     @TargetApi(VERSION_CODES.JELLY_BEAN)
    777     protected static <T> T getFromList(LongSparseArray<T> list, int index) {
    778         if (list == null || index < 0) {
    779             return null;
    780         }
    781         return list.get(index);
    782     }
    783 
    784     /** @hide */
    785     @TargetApi(VERSION_CODES.JELLY_BEAN)
    786     protected static <T> void setTo(LongSparseArray<T> list, int index, T value) {
    787         if (list == null || index < 0 || index >= list.size()) {
    788             return;
    789         }
    790         list.put(index, value);
    791     }
    792 
    793     /** @hide */
    794     protected static <T> T getFromList(android.support.v4.util.LongSparseArray<T> list, int index) {
    795         if (list == null || index < 0) {
    796             return null;
    797         }
    798         return list.get(index);
    799     }
    800 
    801     /** @hide */
    802     protected static <T> void setTo(android.support.v4.util.LongSparseArray<T> list, int index,
    803             T value) {
    804         if (list == null || index < 0 || index >= list.size()) {
    805             return;
    806         }
    807         list.put(index, value);
    808     }
    809 
    810     /** @hide */
    811     protected static boolean getFromList(SparseBooleanArray list, int index) {
    812         if (list == null || index < 0) {
    813             return false;
    814         }
    815         return list.get(index);
    816     }
    817 
    818     /** @hide */
    819     protected static void setTo(SparseBooleanArray list, int index, boolean value) {
    820         if (list == null || index < 0 || index >= list.size()) {
    821             return;
    822         }
    823         list.put(index, value);
    824     }
    825 
    826     /** @hide */
    827     protected static int getFromList(SparseIntArray list, int index) {
    828         if (list == null || index < 0) {
    829             return 0;
    830         }
    831         return list.get(index);
    832     }
    833 
    834     /** @hide */
    835     protected static void setTo(SparseIntArray list, int index, int value) {
    836         if (list == null || index < 0 || index >= list.size()) {
    837             return;
    838         }
    839         list.put(index, value);
    840     }
    841 
    842     /** @hide */
    843     @TargetApi(VERSION_CODES.JELLY_BEAN_MR2)
    844     protected static long getFromList(SparseLongArray list, int index) {
    845         if (list == null || index < 0) {
    846             return 0;
    847         }
    848         return list.get(index);
    849     }
    850 
    851     /** @hide */
    852     @TargetApi(VERSION_CODES.JELLY_BEAN_MR2)
    853     protected static void setTo(SparseLongArray list, int index, long value) {
    854         if (list == null || index < 0 || index >= list.size()) {
    855             return;
    856         }
    857         list.put(index, value);
    858     }
    859 
    860     /** @hide */
    861     protected static <K, T> T getFrom(Map<K, T> map, K key) {
    862         if (map == null) {
    863             return null;
    864         }
    865         return map.get(key);
    866     }
    867 
    868     /** @hide */
    869     protected static <K, T> void setTo(Map<K, T> map, K key, T value) {
    870         if (map == null) {
    871             return;
    872         }
    873         map.put(key, value);
    874     }
    875 
    876     /** @hide */
    877     protected static void setBindingInverseListener(ViewDataBinding binder,
    878             InverseBindingListener oldListener, PropertyChangedInverseListener listener) {
    879         if (oldListener != listener) {
    880             if (oldListener != null) {
    881                 binder.removeOnPropertyChangedCallback(
    882                         (PropertyChangedInverseListener) oldListener);
    883             }
    884             if (listener != null) {
    885                 binder.addOnPropertyChangedCallback(listener);
    886             }
    887         }
    888     }
    889 
    890     /**
    891      * Walks the view hierarchy under roots and pulls out tagged Views, includes, and views with
    892      * IDs into an Object[] that is returned. This is used to walk the view hierarchy once to find
    893      * all bound and ID'd views.
    894      *
    895      * @param bindingComponent The binding component to use with this binding.
    896      * @param roots The root Views of the view hierarchy to walk. This is used with merge tags.
    897      * @param numBindings The total number of ID'd views, views with expressions, and includes
    898      * @param includes The include layout information, indexed by their container's index.
    899      * @param viewsWithIds Indexes of views that don't have tags, but have IDs.
    900      * @return An array of size numBindings containing all Views in the hierarchy that have IDs
    901      * (with elements in viewsWithIds), are tagged containing expressions, or the bindings for
    902      * included layouts.
    903      * @hide
    904      */
    905     protected static Object[] mapBindings(DataBindingComponent bindingComponent, View[] roots,
    906             int numBindings, IncludedLayouts includes, SparseIntArray viewsWithIds) {
    907         Object[] bindings = new Object[numBindings];
    908         for (int i = 0; i < roots.length; i++) {
    909             mapBindings(bindingComponent, roots[i], bindings, includes, viewsWithIds, true);
    910         }
    911         return bindings;
    912     }
    913 
    914     private static void mapBindings(DataBindingComponent bindingComponent, View view,
    915             Object[] bindings, IncludedLayouts includes, SparseIntArray viewsWithIds,
    916             boolean isRoot) {
    917         final int indexInIncludes;
    918         final ViewDataBinding existingBinding = getBinding(view);
    919         if (existingBinding != null) {
    920             return;
    921         }
    922         final String tag = (String) view.getTag();
    923         boolean isBound = false;
    924         if (isRoot && tag != null && tag.startsWith("layout")) {
    925             final int underscoreIndex = tag.lastIndexOf('_');
    926             if (underscoreIndex > 0 && isNumeric(tag, underscoreIndex + 1)) {
    927                 final int index = parseTagInt(tag, underscoreIndex + 1);
    928                 if (bindings[index] == null) {
    929                     bindings[index] = view;
    930                 }
    931                 indexInIncludes = includes == null ? -1 : index;
    932                 isBound = true;
    933             } else {
    934                 indexInIncludes = -1;
    935             }
    936         } else if (tag != null && tag.startsWith(BINDING_TAG_PREFIX)) {
    937             int tagIndex = parseTagInt(tag, BINDING_NUMBER_START);
    938             if (bindings[tagIndex] == null) {
    939                 bindings[tagIndex] = view;
    940             }
    941             isBound = true;
    942             indexInIncludes = includes == null ? -1 : tagIndex;
    943         } else {
    944             // Not a bound view
    945             indexInIncludes = -1;
    946         }
    947         if (!isBound) {
    948             final int id = view.getId();
    949             if (id > 0) {
    950                 int index;
    951                 if (viewsWithIds != null && (index = viewsWithIds.get(id, -1)) >= 0 &&
    952                         bindings[index] == null) {
    953                     bindings[index] = view;
    954                 }
    955             }
    956         }
    957 
    958         if (view instanceof  ViewGroup) {
    959             final ViewGroup viewGroup = (ViewGroup) view;
    960             final int count = viewGroup.getChildCount();
    961             int minInclude = 0;
    962             for (int i = 0; i < count; i++) {
    963                 final View child = viewGroup.getChildAt(i);
    964                 boolean isInclude = false;
    965                 if (indexInIncludes >= 0) {
    966                     String childTag = (String) child.getTag();
    967                     if (childTag != null && childTag.endsWith("_0") &&
    968                             childTag.startsWith("layout") && childTag.indexOf('/') > 0) {
    969                         // This *could* be an include. Test against the expected includes.
    970                         int includeIndex = findIncludeIndex(childTag, minInclude,
    971                                 includes, indexInIncludes);
    972                         if (includeIndex >= 0) {
    973                             isInclude = true;
    974                             minInclude = includeIndex + 1;
    975                             final int index = includes.indexes[indexInIncludes][includeIndex];
    976                             final int layoutId = includes.layoutIds[indexInIncludes][includeIndex];
    977                             int lastMatchingIndex = findLastMatching(viewGroup, i);
    978                             if (lastMatchingIndex == i) {
    979                                 bindings[index] = DataBindingUtil.bind(bindingComponent, child,
    980                                         layoutId);
    981                             } else {
    982                                 final int includeCount =  lastMatchingIndex - i + 1;
    983                                 final View[] included = new View[includeCount];
    984                                 for (int j = 0; j < includeCount; j++) {
    985                                     included[j] = viewGroup.getChildAt(i + j);
    986                                 }
    987                                 bindings[index] = DataBindingUtil.bind(bindingComponent, included,
    988                                         layoutId);
    989                                 i += includeCount - 1;
    990                             }
    991                         }
    992                     }
    993                 }
    994                 if (!isInclude) {
    995                     mapBindings(bindingComponent, child, bindings, includes, viewsWithIds, false);
    996                 }
    997             }
    998         }
    999     }
   1000 
   1001     private static int findIncludeIndex(String tag, int minInclude,
   1002             IncludedLayouts included, int includedIndex) {
   1003         final int slashIndex = tag.indexOf('/');
   1004         final CharSequence layoutName = tag.subSequence(slashIndex + 1, tag.length() - 2);
   1005 
   1006         final String[] layouts = included.layouts[includedIndex];
   1007         final int length = layouts.length;
   1008         for (int i = minInclude; i < length; i++) {
   1009             final String layout = layouts[i];
   1010             if (TextUtils.equals(layoutName, layout)) {
   1011                 return i;
   1012             }
   1013         }
   1014         return -1;
   1015     }
   1016 
   1017     private static int findLastMatching(ViewGroup viewGroup, int firstIncludedIndex) {
   1018         final View firstView = viewGroup.getChildAt(firstIncludedIndex);
   1019         final String firstViewTag = (String) firstView.getTag();
   1020         final String tagBase = firstViewTag.substring(0, firstViewTag.length() - 1); // don't include the "0"
   1021         final int tagSequenceIndex = tagBase.length();
   1022 
   1023         final int count = viewGroup.getChildCount();
   1024         int max = firstIncludedIndex;
   1025         for (int i = firstIncludedIndex + 1; i < count; i++) {
   1026             final View view = viewGroup.getChildAt(i);
   1027             final String tag = (String) view.getTag();
   1028             if (tag != null && tag.startsWith(tagBase)) {
   1029                 if (tag.length() == firstViewTag.length() && tag.charAt(tag.length() - 1) == '0') {
   1030                     return max; // Found another instance of the include
   1031                 }
   1032                 if (isNumeric(tag, tagSequenceIndex)) {
   1033                     max = i;
   1034                 }
   1035             }
   1036         }
   1037         return max;
   1038     }
   1039 
   1040     private static boolean isNumeric(String tag, int startIndex) {
   1041         int length = tag.length();
   1042         if (length == startIndex) {
   1043             return false; // no numerals
   1044         }
   1045         for (int i = startIndex; i < length; i++) {
   1046             if (!Character.isDigit(tag.charAt(i))) {
   1047                 return false;
   1048             }
   1049         }
   1050         return true;
   1051     }
   1052 
   1053     /**
   1054      * Parse the tag without creating a new String object. This is fast and assumes the
   1055      * tag is in the correct format.
   1056      * @param str The tag string.
   1057      * @return The binding tag number parsed from the tag string.
   1058      */
   1059     private static int parseTagInt(String str, int startIndex) {
   1060         final int end = str.length();
   1061         int val = 0;
   1062         for (int i = startIndex; i < end; i++) {
   1063             val *= 10;
   1064             char c = str.charAt(i);
   1065             val += (c - '0');
   1066         }
   1067         return val;
   1068     }
   1069 
   1070     private interface ObservableReference<T> {
   1071         WeakListener<T> getListener();
   1072         void addListener(T target);
   1073         void removeListener(T target);
   1074     }
   1075 
   1076     private static class WeakListener<T> extends WeakReference<ViewDataBinding> {
   1077         private final ObservableReference<T> mObservable;
   1078         protected final int mLocalFieldId;
   1079         private T mTarget;
   1080 
   1081         public WeakListener(ViewDataBinding binder, int localFieldId,
   1082                 ObservableReference<T> observable) {
   1083             super(binder);
   1084             mLocalFieldId = localFieldId;
   1085             mObservable = observable;
   1086         }
   1087 
   1088         public void setTarget(T object) {
   1089             unregister();
   1090             mTarget = object;
   1091             if (mTarget != null) {
   1092                 mObservable.addListener(mTarget);
   1093             }
   1094         }
   1095 
   1096         public boolean unregister() {
   1097             boolean unregistered = false;
   1098             if (mTarget != null) {
   1099                 mObservable.removeListener(mTarget);
   1100                 unregistered = true;
   1101             }
   1102             mTarget = null;
   1103             return unregistered;
   1104         }
   1105 
   1106         public T getTarget() {
   1107             return mTarget;
   1108         }
   1109 
   1110         protected ViewDataBinding getBinder() {
   1111             ViewDataBinding binder = get();
   1112             if (binder == null) {
   1113                 unregister(); // The binder is dead
   1114             }
   1115             return binder;
   1116         }
   1117     }
   1118 
   1119     private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback
   1120             implements ObservableReference<Observable> {
   1121         final WeakListener<Observable> mListener;
   1122 
   1123         public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
   1124             mListener = new WeakListener<Observable>(binder, localFieldId, this);
   1125         }
   1126 
   1127         @Override
   1128         public WeakListener<Observable> getListener() {
   1129             return mListener;
   1130         }
   1131 
   1132         @Override
   1133         public void addListener(Observable target) {
   1134             target.addOnPropertyChangedCallback(this);
   1135         }
   1136 
   1137         @Override
   1138         public void removeListener(Observable target) {
   1139             target.removeOnPropertyChangedCallback(this);
   1140         }
   1141 
   1142         @Override
   1143         public void onPropertyChanged(Observable sender, int propertyId) {
   1144             ViewDataBinding binder = mListener.getBinder();
   1145             if (binder == null) {
   1146                 return;
   1147             }
   1148             Observable obj = mListener.getTarget();
   1149             if (obj != sender) {
   1150                 return; // notification from the wrong object?
   1151             }
   1152             binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
   1153         }
   1154     }
   1155 
   1156     private static class WeakListListener extends ObservableList.OnListChangedCallback
   1157             implements ObservableReference<ObservableList> {
   1158         final WeakListener<ObservableList> mListener;
   1159 
   1160         public WeakListListener(ViewDataBinding binder, int localFieldId) {
   1161             mListener = new WeakListener<ObservableList>(binder, localFieldId, this);
   1162         }
   1163 
   1164         @Override
   1165         public WeakListener<ObservableList> getListener() {
   1166             return mListener;
   1167         }
   1168 
   1169         @Override
   1170         public void addListener(ObservableList target) {
   1171             target.addOnListChangedCallback(this);
   1172         }
   1173 
   1174         @Override
   1175         public void removeListener(ObservableList target) {
   1176             target.removeOnListChangedCallback(this);
   1177         }
   1178 
   1179         @Override
   1180         public void onChanged(ObservableList sender) {
   1181             ViewDataBinding binder = mListener.getBinder();
   1182             if (binder == null) {
   1183                 return;
   1184             }
   1185             ObservableList target = mListener.getTarget();
   1186             if (target != sender) {
   1187                 return; // We expect notifications only from sender
   1188             }
   1189             binder.handleFieldChange(mListener.mLocalFieldId, target, 0);
   1190         }
   1191 
   1192         @Override
   1193         public void onItemRangeChanged(ObservableList sender, int positionStart, int itemCount) {
   1194             onChanged(sender);
   1195         }
   1196 
   1197         @Override
   1198         public void onItemRangeInserted(ObservableList sender, int positionStart, int itemCount) {
   1199             onChanged(sender);
   1200         }
   1201 
   1202         @Override
   1203         public void onItemRangeMoved(ObservableList sender, int fromPosition, int toPosition,
   1204                 int itemCount) {
   1205             onChanged(sender);
   1206         }
   1207 
   1208         @Override
   1209         public void onItemRangeRemoved(ObservableList sender, int positionStart, int itemCount) {
   1210             onChanged(sender);
   1211         }
   1212     }
   1213 
   1214     private static class WeakMapListener extends ObservableMap.OnMapChangedCallback
   1215             implements ObservableReference<ObservableMap> {
   1216         final WeakListener<ObservableMap> mListener;
   1217 
   1218         public WeakMapListener(ViewDataBinding binder, int localFieldId) {
   1219             mListener = new WeakListener<ObservableMap>(binder, localFieldId, this);
   1220         }
   1221 
   1222         @Override
   1223         public WeakListener<ObservableMap> getListener() {
   1224             return mListener;
   1225         }
   1226 
   1227         @Override
   1228         public void addListener(ObservableMap target) {
   1229             target.addOnMapChangedCallback(this);
   1230         }
   1231 
   1232         @Override
   1233         public void removeListener(ObservableMap target) {
   1234             target.removeOnMapChangedCallback(this);
   1235         }
   1236 
   1237         @Override
   1238         public void onMapChanged(ObservableMap sender, Object key) {
   1239             ViewDataBinding binder = mListener.getBinder();
   1240             if (binder == null || sender != mListener.getTarget()) {
   1241                 return;
   1242             }
   1243             binder.handleFieldChange(mListener.mLocalFieldId, sender, 0);
   1244         }
   1245     }
   1246 
   1247     private interface CreateWeakListener {
   1248         WeakListener create(ViewDataBinding viewDataBinding, int localFieldId);
   1249     }
   1250 
   1251     /**
   1252      * This class is used by generated subclasses of {@link ViewDataBinding} to track the
   1253      * included layouts contained in the bound layout. This class is an implementation
   1254      * detail of how binding expressions are mapped to Views after inflation.
   1255      * @hide
   1256      */
   1257     protected static class IncludedLayouts {
   1258         public final String[][] layouts;
   1259         public final int[][] indexes;
   1260         public final int[][] layoutIds;
   1261 
   1262         public IncludedLayouts(int bindingCount) {
   1263             layouts = new String[bindingCount][];
   1264             indexes = new int[bindingCount][];
   1265             layoutIds = new int[bindingCount][];
   1266         }
   1267 
   1268         public void setIncludes(int index, String[] layouts, int[] indexes, int[] layoutIds) {
   1269             this.layouts[index] = layouts;
   1270             this.indexes[index] = indexes;
   1271             this.layoutIds[index] = layoutIds;
   1272         }
   1273     }
   1274 
   1275     /**
   1276      * This class is used by generated subclasses of {@link ViewDataBinding} to listen for
   1277      * changes on variables of Bindings. This is important for two-way data binding on variables
   1278      * in included Bindings.
   1279      * @hide
   1280      */
   1281     protected static abstract class PropertyChangedInverseListener
   1282             extends Observable.OnPropertyChangedCallback implements InverseBindingListener {
   1283         final int mPropertyId;
   1284 
   1285         public PropertyChangedInverseListener(int propertyId) {
   1286             mPropertyId = propertyId;
   1287         }
   1288 
   1289         @Override
   1290         public void onPropertyChanged(Observable sender, int propertyId) {
   1291             if (propertyId == mPropertyId || propertyId == 0) {
   1292                 onChange();
   1293             }
   1294         }
   1295     }
   1296 }
   1297