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