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