• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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 com.android.internal.widget;
18 
19 import static android.widget.flags.Flags.notifLinearlayoutOptimized;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.Context;
24 import android.content.res.Resources;
25 import android.graphics.drawable.Drawable;
26 import android.os.Build;
27 import android.os.Trace;
28 import android.util.AttributeSet;
29 import android.util.Log;
30 import android.view.Gravity;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.widget.LinearLayout;
34 import android.widget.RemoteViews;
35 
36 import java.util.ArrayList;
37 import java.util.List;
38 
39 /**
40  * This LinearLayout customizes the measurement behavior of LinearLayout for Notification layouts.
41  * When there is exactly
42  * one child View with <code>layout_weight</code>. onMeasure methods of this LinearLayout will:
43  * 1. Measure all other children.
44  * 2. Calculate the remaining space for the View with <code>layout_weight</code>
45  * 3. Measure the weighted View using the calculated remaining width or height (based on
46  * Orientation).
47  * This ensures that the weighted View fills the remaining space in LinearLayout with only single
48  * measure.
49  *
50  * **Assumptions:**
51  * - There is *exactly one* child view with non-zero <code>layout_weight</code>.
52  * - Other views should not have weight.
53  * - LinearLayout doesn't have <code>weightSum</code>.
54  * - Horizontal LinearLayout's width should be measured EXACTLY.
55  * - Horizontal LinearLayout shouldn't need baseLineAlignment.
56  * - Horizontal LinearLayout shouldn't have any child that has negative left or right margin.
57  * - Vertical LinearLayout shouldn't have MATCH_PARENT children when it is not measured EXACTLY.
58  *
59  * @hide
60  */
61 @RemoteViews.RemoteView
62 public class NotificationOptimizedLinearLayout extends LinearLayout {
63     private static final boolean DEBUG_LAYOUT = false;
64     private static final boolean TRACE_ONMEASURE = Build.isDebuggable();
65     private static final String TAG = "NotifOptimizedLinearLayout";
66 
67     private boolean mShouldUseOptimizedLayout = false;
68 
NotificationOptimizedLinearLayout(Context context)69     public NotificationOptimizedLinearLayout(Context context) {
70         super(context);
71     }
72 
NotificationOptimizedLinearLayout(Context context, @Nullable AttributeSet attrs)73     public NotificationOptimizedLinearLayout(Context context, @Nullable AttributeSet attrs) {
74         super(context, attrs);
75     }
76 
NotificationOptimizedLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr)77     public NotificationOptimizedLinearLayout(Context context, @Nullable AttributeSet attrs,
78             int defStyleAttr) {
79         super(context, attrs, defStyleAttr);
80     }
81 
NotificationOptimizedLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)82     public NotificationOptimizedLinearLayout(Context context, AttributeSet attrs, int defStyleAttr,
83             int defStyleRes) {
84         super(context, attrs, defStyleAttr, defStyleRes);
85     }
86 
87     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)88     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
89         final View weightedChildView = getSingleWeightedChild();
90         mShouldUseOptimizedLayout =
91                 isUseOptimizedLinearLayoutFlagEnabled() && weightedChildView != null
92                         && isOptimizationPossible(widthMeasureSpec, heightMeasureSpec);
93 
94         if (mShouldUseOptimizedLayout) {
95             onMeasureOptimized(weightedChildView, widthMeasureSpec, heightMeasureSpec);
96         } else {
97             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
98         }
99     }
100 
isUseOptimizedLinearLayoutFlagEnabled()101     private boolean isUseOptimizedLinearLayoutFlagEnabled() {
102         final boolean enabled = notifLinearlayoutOptimized();
103         if (!enabled) {
104             logSkipOptimizedOnMeasure("enableNotifLinearlayoutOptimized flag is off.");
105         }
106         return enabled;
107     }
108 
109     /**
110      * Checks if optimizations can be safely applied to this LinearLayout during layout
111      * calculations. Optimizations might be disabled in the following cases:
112      *
113      * **weightSum**: When LinearLayout has weightSum
114      * ** MATCH_PARENT children in non EXACT dimension**
115      * **Horizontal LinearLayout with non-EXACT width**
116      * **Baseline Alignment:**  If views need to align their baselines in Horizontal LinearLayout
117      *
118      * @param widthMeasureSpec  The width measurement specification.
119      * @param heightMeasureSpec The height measurement specification.
120      * @return `true` if optimization is possible, `false` otherwise.
121      */
isOptimizationPossible(int widthMeasureSpec, int heightMeasureSpec)122     private boolean isOptimizationPossible(int widthMeasureSpec, int heightMeasureSpec) {
123         final boolean hasWeightSum = getWeightSum() > 0.0f;
124         if (hasWeightSum) {
125             logSkipOptimizedOnMeasure("Has weightSum.");
126             return false;
127         }
128 
129         if (requiresMatchParentRemeasureForVerticalLinearLayout(widthMeasureSpec)) {
130             logSkipOptimizedOnMeasure(
131                     "Vertical LinearLayout requires children width MATCH_PARENT remeasure ");
132             return false;
133         }
134 
135         final boolean isHorizontal = getOrientation() == HORIZONTAL;
136         if (isHorizontal && MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) {
137             logSkipOptimizedOnMeasure("Horizontal LinearLayout's width should be "
138                     + "measured EXACTLY");
139             return false;
140         }
141 
142         if (requiresBaselineAlignmentForHorizontalLinearLayout()) {
143             logSkipOptimizedOnMeasure("Need to apply baseline.");
144             return false;
145         }
146 
147         if (requiresNegativeMarginHandlingForHorizontalLinearLayout()) {
148             logSkipOptimizedOnMeasure("Need to handle negative margins.");
149             return false;
150         }
151         return true;
152     }
153 
154     /**
155      * @return if the horizontal linearlayout requires to handle negative margins in its children.
156      * In that case, we can't use excessSpace because LinearLayout negative margin handling for
157      * excess space and WRAP_CONTENT is different.
158      */
requiresNegativeMarginHandlingForHorizontalLinearLayout()159     private boolean requiresNegativeMarginHandlingForHorizontalLinearLayout() {
160         if (getOrientation() == VERTICAL) {
161             return false;
162         }
163 
164         final List<View> activeChildren = getActiveChildren();
165         for (int i = 0; i < activeChildren.size(); i++) {
166             final View child = activeChildren.get(i);
167             final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
168             if (lp.leftMargin < 0 || lp.rightMargin < 0) {
169                 return true;
170             }
171         }
172         return false;
173     }
174 
175     /**
176      * @return if the vertical linearlayout requires match_parent children remeasure
177      */
requiresMatchParentRemeasureForVerticalLinearLayout(int widthMeasureSpec)178     private boolean requiresMatchParentRemeasureForVerticalLinearLayout(int widthMeasureSpec) {
179         // HORIZONTAL measuring is handled by LinearLayout. That's why we don't need to check it
180         // here.
181         if (getOrientation() == HORIZONTAL) {
182             return false;
183         }
184 
185         // When the width is not EXACT, children with MATCH_PARENT width need to be double measured.
186         // This needs to be handled in LinearLayout because NotificationOptimizedLinearLayout
187         final boolean nonExactWidth =
188                 MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY;
189         final List<View> activeChildren = getActiveChildren();
190         for (int i = 0; i < activeChildren.size(); i++) {
191             final View child = activeChildren.get(i);
192             final ViewGroup.LayoutParams lp = child.getLayoutParams();
193             if (nonExactWidth && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
194                 return true;
195             }
196         }
197         return false;
198     }
199 
200     /**
201      * @return if this layout needs to apply baseLineAlignment.
202      */
requiresBaselineAlignmentForHorizontalLinearLayout()203     private boolean requiresBaselineAlignmentForHorizontalLinearLayout() {
204         // baseLineAlignment is not important for Vertical LinearLayout.
205         if (getOrientation() == VERTICAL) {
206             return false;
207         }
208         // Early return, if it is already disabled
209         if (!isBaselineAligned()) {
210             return false;
211         }
212 
213         final List<View> activeChildren = getActiveChildren();
214         final int minorGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
215 
216         for (int i = 0; i < activeChildren.size(); i++) {
217             final View child = activeChildren.get(i);
218             if (child.getLayoutParams() instanceof LayoutParams) {
219                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
220                 int childBaseline = -1;
221 
222                 if (lp.height != LayoutParams.MATCH_PARENT) {
223                     childBaseline = child.getBaseline();
224                 }
225                 if (childBaseline == -1) {
226                     // This child doesn't have a baseline.
227                     continue;
228                 }
229                 int gravity = lp.gravity;
230                 if (gravity < 0) {
231                     gravity = minorGravity;
232                 }
233 
234                 final int result = gravity & Gravity.VERTICAL_GRAVITY_MASK;
235                 if (result == Gravity.TOP || result == Gravity.BOTTOM) {
236                     return true;
237                 }
238             }
239         }
240         return false;
241     }
242 
243     /**
244      * Finds the single child view within this layout that has a non-zero weight assigned to its
245      * LayoutParams.
246      *
247      * @return The weighted child view, or null if multiple weighted children exist or no weighted
248      * children are found.
249      */
250     @Nullable
getSingleWeightedChild()251     private View getSingleWeightedChild() {
252         final boolean isVertical = getOrientation() == VERTICAL;
253         final List<View> activeChildren = getActiveChildren();
254         View singleWeightedChild = null;
255         for (int i = 0; i < activeChildren.size(); i++) {
256             final View child = activeChildren.get(i);
257             if (child.getLayoutParams() instanceof LayoutParams) {
258                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
259                 if ((!isVertical && lp.width == ViewGroup.LayoutParams.MATCH_PARENT)
260                         || (isVertical && lp.height == ViewGroup.LayoutParams.MATCH_PARENT)) {
261                     logSkipOptimizedOnMeasure(
262                             "There is a match parent child in the related orientation.");
263                     return null;
264                 }
265                 if (lp.weight != 0) {
266                     if (singleWeightedChild == null) {
267                         singleWeightedChild = child;
268                     } else {
269                         logSkipOptimizedOnMeasure("There is more than one weighted child.");
270                         return null;
271                     }
272                 }
273             }
274         }
275         if (singleWeightedChild == null) {
276             logSkipOptimizedOnMeasure("There is no weighted child in this layout.");
277         } else {
278             final LayoutParams lp = (LayoutParams) singleWeightedChild.getLayoutParams();
279             boolean isHeightWrapContentOrZero =
280                     lp.height == ViewGroup.LayoutParams.WRAP_CONTENT || lp.height == 0;
281             boolean isWidthWrapContentOrZero =
282                     lp.width == ViewGroup.LayoutParams.WRAP_CONTENT || lp.width == 0;
283             if ((isVertical && !isHeightWrapContentOrZero)
284                     || (!isVertical && !isWidthWrapContentOrZero)) {
285                 logSkipOptimizedOnMeasure(
286                         "Single weighted child should be either WRAP_CONTENT or 0"
287                                 + " in the related orientation");
288                 singleWeightedChild = null;
289             }
290         }
291 
292         return singleWeightedChild;
293     }
294 
295     /**
296      * Optimized measurement for the single weighted child in this LinearLayout.
297      * Measures other children, calculates remaining space, then measures the weighted
298      * child using the remaining width (or height).
299      *
300      * Note: Horizontal LinearLayout doesn't need to apply baseline in optimized case @see
301      * {@link #requiresBaselineAlignmentForHorizontalLinearLayout}.
302      *
303      * @param weightedChildView The weighted child view(with `layout_weight!=0`)
304      * @param widthMeasureSpec  The width MeasureSpec to use for measurement
305      * @param heightMeasureSpec The height MeasureSpec to use for measurement.
306      */
onMeasureOptimized(@onNull View weightedChildView, int widthMeasureSpec, int heightMeasureSpec)307     private void onMeasureOptimized(@NonNull View weightedChildView, int widthMeasureSpec,
308             int heightMeasureSpec) {
309         try {
310             if (TRACE_ONMEASURE) {
311                 Trace.beginSection("NotifOptimizedLinearLayout#onMeasure");
312             }
313 
314             if (getOrientation() == LinearLayout.HORIZONTAL) {
315                 final ViewGroup.LayoutParams lp = weightedChildView.getLayoutParams();
316                 final int childWidth = lp.width;
317                 final boolean isBaselineAligned = isBaselineAligned();
318                 // It should be marked 0 so that it use excessSpace in LinearLayout's onMeasure
319                 lp.width = 0;
320 
321                 // It doesn't need to apply baseline. So disable it.
322                 setBaselineAligned(false);
323                 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
324 
325                 // restore values.
326                 lp.width = childWidth;
327                 setBaselineAligned(isBaselineAligned);
328             } else {
329                 measureVerticalOptimized(weightedChildView, widthMeasureSpec, heightMeasureSpec);
330             }
331         } finally {
332             if (TRACE_ONMEASURE) {
333                 trackShouldUseOptimizedLayout();
334                 Trace.endSection();
335             }
336         }
337     }
338 
339     @Override
onLayout(boolean changed, int l, int t, int r, int b)340     protected void onLayout(boolean changed, int l, int t, int r, int b) {
341         if (mShouldUseOptimizedLayout) {
342             onLayoutOptimized(changed, l, t, r, b);
343         } else {
344             super.onLayout(changed, l, t, r, b);
345         }
346     }
347 
onLayoutOptimized(boolean changed, int l, int t, int r, int b)348     private void onLayoutOptimized(boolean changed, int l, int t, int r, int b) {
349         if (getOrientation() == LinearLayout.HORIZONTAL) {
350             super.onLayout(changed, l, t, r, b);
351         } else {
352             layoutVerticalOptimized(l, t, r, b);
353         }
354     }
355 
356     /**
357      * Optimized measurement for the single weighted child in this LinearLayout.
358      * Measures other children, calculates remaining space, then measures the weighted
359      * child using the exact remaining height.
360      *
361      * @param weightedChildView The weighted child view(with `layout_weight=1`
362      * @param widthMeasureSpec  The width MeasureSpec to use for measurement
363      * @param heightMeasureSpec The height MeasureSpec to use for measurement.
364      */
measureVerticalOptimized(@onNull View weightedChildView, int widthMeasureSpec, int heightMeasureSpec)365     private void measureVerticalOptimized(@NonNull View weightedChildView, int widthMeasureSpec,
366             int heightMeasureSpec) {
367         int totalLength = 0;
368         int maxWidth = 0;
369         final int availableHeight = MeasureSpec.getSize(heightMeasureSpec);
370         final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
371 
372         // 1. Measure all unweighted children
373         for (int i = 0; i < getChildCount(); i++) {
374             final View child = getChildAt(i);
375             if (child == null || child.getVisibility() == GONE) {
376                 continue;
377             }
378 
379             final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
380 
381             if (child == weightedChildView) {
382                 // In excessMode, LinearLayout add  weighted child top and bottom margins to
383                 // totalLength when their sum is positive.
384                 if (lp.height == 0 && heightMode == MeasureSpec.EXACTLY) {
385                     totalLength = Math.max(totalLength, totalLength + lp.topMargin
386                             + lp.bottomMargin);
387                 }
388                 continue;
389             }
390 
391             measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
392             // LinearLayout only adds measured children heights and its top and bottom margins
393             // to totalLength when their sum is positive.
394             totalLength = Math.max(totalLength,
395                     totalLength + child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
396             maxWidth = Math.max(maxWidth,
397                     child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
398         }
399 
400         // Add padding to totalLength that we are going to use for remaining space.
401         totalLength += mPaddingTop + mPaddingBottom;
402 
403         // 2. generate measure spec for weightedChildView.
404         final MarginLayoutParams lp = (MarginLayoutParams) weightedChildView.getLayoutParams();
405         // height should be AT_MOST for non EXACT cases.
406         final int childHeightMeasureMode =
407                 heightMode == MeasureSpec.EXACTLY ? MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
408         final int childHeightMeasureSpec;
409 
410         // In excess mode, LinearLayout measures weighted children with remaining space. Otherwise,
411         // it is measured with remaining space just like other children.
412         if (lp.height == 0 && heightMode == MeasureSpec.EXACTLY) {
413             childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
414                     Math.max(0, availableHeight - totalLength), childHeightMeasureMode);
415         } else {
416             final int usedHeight = lp.topMargin + lp.bottomMargin + totalLength;
417             childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
418                     Math.max(0, availableHeight - usedHeight), childHeightMeasureMode);
419         }
420         final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
421                 mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin, lp.width);
422 
423         // 3. Measure weightedChildView with the remaining space.
424         weightedChildView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
425 
426         totalLength = Math.max(totalLength,
427                 totalLength + weightedChildView.getMeasuredHeight() + lp.topMargin
428                         + lp.bottomMargin);
429 
430         maxWidth = Math.max(maxWidth,
431                 weightedChildView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
432 
433         // Add padding to width
434         maxWidth += getPaddingLeft() + getPaddingRight();
435 
436         // Resolve final dimensions
437         final int finalWidth = resolveSizeAndState(Math.max(maxWidth, getSuggestedMinimumWidth()),
438                 widthMeasureSpec, 0);
439         final int finalHeight = resolveSizeAndState(
440                 Math.max(totalLength, getSuggestedMinimumHeight()), heightMeasureSpec, 0);
441         setMeasuredDimension(finalWidth, finalHeight);
442     }
443 
444     @NonNull
getActiveChildren()445     private List<View> getActiveChildren() {
446         final int childCount = getChildCount();
447         final List<View> activeChildren = new ArrayList<>();
448         for (int i = 0; i < childCount; i++) {
449             final View child = getChildAt(i);
450             if (child == null || child.getVisibility() == View.GONE) {
451                 continue;
452             }
453             activeChildren.add(child);
454         }
455         return activeChildren;
456     }
457 
458     //region LinearLayout copy methods
459 
460     /**
461      * layoutVerticalOptimized is a version of LinearLayout's layoutVertical method that
462      * excludes
463      * TableRow-related functionalities.
464      *
465      * @see LinearLayout#onLayout(boolean, int, int, int, int)
466      */
layoutVerticalOptimized(int left, int top, int right, int bottom)467     private void layoutVerticalOptimized(int left, int top, int right,
468             int bottom) {
469         final int paddingLeft = mPaddingLeft;
470         final int mTotalLength = getMeasuredHeight();
471         int childTop;
472         int childLeft;
473 
474         // Where right end of child should go
475         final int width = right - left;
476         int childRight = width - mPaddingRight;
477 
478         // Space available for child
479         int childSpace = width - paddingLeft - mPaddingRight;
480 
481         final int count = getChildCount();
482 
483         final int majorGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
484         final int minorGravity = getGravity() & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
485 
486         switch (majorGravity) {
487             case Gravity.BOTTOM:
488                 // mTotalLength contains the padding already
489                 childTop = mPaddingTop + bottom - top - mTotalLength;
490                 break;
491 
492             // mTotalLength contains the padding already
493             case Gravity.CENTER_VERTICAL:
494                 childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
495                 break;
496 
497             case Gravity.TOP:
498             default:
499                 childTop = mPaddingTop;
500                 break;
501         }
502         final int dividerHeight = getDividerHeight();
503         for (int i = 0; i < count; i++) {
504             final View child = getChildAt(i);
505             if (child != null && child.getVisibility() != GONE) {
506                 final int childWidth = child.getMeasuredWidth();
507                 final int childHeight = child.getMeasuredHeight();
508 
509                 final LinearLayout.LayoutParams lp =
510                         (LinearLayout.LayoutParams) child.getLayoutParams();
511 
512                 int gravity = lp.gravity;
513                 if (gravity < 0) {
514                     gravity = minorGravity;
515                 }
516                 final int layoutDirection = getLayoutDirection();
517                 final int absoluteGravity = Gravity.getAbsoluteGravity(gravity,
518                         layoutDirection);
519                 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
520                     case Gravity.CENTER_HORIZONTAL:
521                         childLeft =
522                                 paddingLeft + ((childSpace - childWidth) / 2) + lp.leftMargin
523                                         - lp.rightMargin;
524                         break;
525 
526                     case Gravity.RIGHT:
527                         childLeft = childRight - childWidth - lp.rightMargin;
528                         break;
529 
530                     case Gravity.LEFT:
531                     default:
532                         childLeft = paddingLeft + lp.leftMargin;
533                         break;
534                 }
535 
536                 if (hasDividerBeforeChildAt(i)) {
537                     childTop += dividerHeight;
538                 }
539 
540                 childTop += lp.topMargin;
541                 child.layout(childLeft, childTop, childLeft + childWidth,
542                         childTop + childHeight);
543                 childTop += childHeight + lp.bottomMargin;
544 
545             }
546         }
547     }
548 
549     /**
550      * Used in laying out views vertically.
551      *
552      * @see #layoutVerticalOptimized
553      * @see LinearLayout#onLayout(boolean, int, int, int, int)
554      */
getDividerHeight()555     private int getDividerHeight() {
556         final Drawable dividerDrawable = getDividerDrawable();
557         if (dividerDrawable == null) {
558             return 0;
559         } else {
560             return dividerDrawable.getIntrinsicHeight();
561         }
562     }
563     //endregion
564 
565     //region Logging&Tracing
trackShouldUseOptimizedLayout()566     private void trackShouldUseOptimizedLayout() {
567         if (TRACE_ONMEASURE) {
568             Trace.setCounter("NotifOptimizedLinearLayout#shouldUseOptimizedLayout",
569                     mShouldUseOptimizedLayout ? 1 : 0);
570         }
571     }
572 
logSkipOptimizedOnMeasure(String reason)573     private void logSkipOptimizedOnMeasure(String reason) {
574         if (DEBUG_LAYOUT) {
575             final StringBuilder logMessage = new StringBuilder();
576             int layoutId = getId();
577             if (layoutId != NO_ID) {
578                 final Resources resources = getResources();
579                 if (resources != null) {
580                     logMessage.append("[");
581                     logMessage.append(resources.getResourceName(layoutId));
582                     logMessage.append("] ");
583                 }
584             }
585             logMessage.append("Going to skip onMeasureOptimized reason:");
586             logMessage.append(reason);
587 
588             Log.d(TAG, logMessage.toString());
589         }
590     }
591     //endregion
592 }
593