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 static boolean parse(String str, boolean fallback) {
    574         if (str == null) {
    575             return fallback;
    576         }
    577         return Boolean.parseBoolean(str);
    578     }
    579 
    580     /** @hide */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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)
    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)
    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 */
    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 */
    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 */
    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 */
    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 */
    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 */
    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)
    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)
    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 */
    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 */
    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 */
    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      */
    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 
    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 
   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 
   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 
   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      */
   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> {
   1143         WeakListener<T> getListener();
   1144         void addListener(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 
   1153         public WeakListener(ViewDataBinding binder, int localFieldId,
   1154                 ObservableReference<T> observable) {
   1155             super(binder);
   1156             mLocalFieldId = localFieldId;
   1157             mObservable = observable;
   1158         }
   1159 
   1160         public void setTarget(T object) {
   1161             unregister();
   1162             mTarget = object;
   1163             if (mTarget != null) {
   1164                 mObservable.addListener(mTarget);
   1165             }
   1166         }
   1167 
   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 
   1178         public T getTarget() {
   1179             return mTarget;
   1180         }
   1181 
   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 
   1195         public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
   1196             mListener = new WeakListener<Observable>(binder, localFieldId, this);
   1197         }
   1198 
   1199         @Override
   1200         public WeakListener<Observable> getListener() {
   1201             return mListener;
   1202         }
   1203 
   1204         @Override
   1205         public void addListener(Observable target) {
   1206             target.addOnPropertyChangedCallback(this);
   1207         }
   1208 
   1209         @Override
   1210         public void removeListener(Observable target) {
   1211             target.removeOnPropertyChangedCallback(this);
   1212         }
   1213 
   1214         @Override
   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 
   1232         public WeakListListener(ViewDataBinding binder, int localFieldId) {
   1233             mListener = new WeakListener<ObservableList>(binder, localFieldId, this);
   1234         }
   1235 
   1236         @Override
   1237         public WeakListener<ObservableList> getListener() {
   1238             return mListener;
   1239         }
   1240 
   1241         @Override
   1242         public void addListener(ObservableList target) {
   1243             target.addOnListChangedCallback(this);
   1244         }
   1245 
   1246         @Override
   1247         public void removeListener(ObservableList target) {
   1248             target.removeOnListChangedCallback(this);
   1249         }
   1250 
   1251         @Override
   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
   1265         public void onItemRangeChanged(ObservableList sender, int positionStart, int itemCount) {
   1266             onChanged(sender);
   1267         }
   1268 
   1269         @Override
   1270         public void onItemRangeInserted(ObservableList sender, int positionStart, int itemCount) {
   1271             onChanged(sender);
   1272         }
   1273 
   1274         @Override
   1275         public void onItemRangeMoved(ObservableList sender, int fromPosition, int toPosition,
   1276                 int itemCount) {
   1277             onChanged(sender);
   1278         }
   1279 
   1280         @Override
   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 
   1290         public WeakMapListener(ViewDataBinding binder, int localFieldId) {
   1291             mListener = new WeakListener<ObservableMap>(binder, localFieldId, this);
   1292         }
   1293 
   1294         @Override
   1295         public WeakListener<ObservableMap> getListener() {
   1296             return mListener;
   1297         }
   1298 
   1299         @Override
   1300         public void addListener(ObservableMap target) {
   1301             target.addOnMapChangedCallback(this);
   1302         }
   1303 
   1304         @Override
   1305         public void removeListener(ObservableMap target) {
   1306             target.removeOnMapChangedCallback(this);
   1307         }
   1308 
   1309         @Override
   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 {
   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 
   1334         public IncludedLayouts(int bindingCount) {
   1335             layouts = new String[bindingCount][];
   1336             indexes = new int[bindingCount][];
   1337             layoutIds = new int[bindingCount][];
   1338         }
   1339 
   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 
   1357         public PropertyChangedInverseListener(int propertyId) {
   1358             mPropertyId = propertyId;
   1359         }
   1360 
   1361         @Override
   1362         public void onPropertyChanged(Observable sender, int propertyId) {
   1363             if (propertyId == mPropertyId || propertyId == 0) {
   1364                 onChange();
   1365             }
   1366         }
   1367     }
   1368 }
   1369