1 /*
2  * Copyright (C) 2019 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 androidx.constraintlayout.core.widgets.analyzer;
18 
19 import static androidx.constraintlayout.core.widgets.ConstraintWidget.GONE;
20 import static androidx.constraintlayout.core.widgets.ConstraintWidget.HORIZONTAL;
21 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_SPREAD;
22 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_WRAP;
23 import static androidx.constraintlayout.core.widgets.ConstraintWidget.VERTICAL;
24 
25 import androidx.constraintlayout.core.LinearSystem;
26 import androidx.constraintlayout.core.widgets.Barrier;
27 import androidx.constraintlayout.core.widgets.ConstraintAnchor;
28 import androidx.constraintlayout.core.widgets.ConstraintWidget;
29 import androidx.constraintlayout.core.widgets.ConstraintWidgetContainer;
30 import androidx.constraintlayout.core.widgets.Guideline;
31 import androidx.constraintlayout.core.widgets.Helper;
32 import androidx.constraintlayout.core.widgets.Optimizer;
33 import androidx.constraintlayout.core.widgets.VirtualLayout;
34 
35 import java.util.ArrayList;
36 
37 /**
38  * Implements basic measure for linear resolution
39  */
40 public class BasicMeasure {
41 
42     private static final boolean DEBUG = false;
43     private static final boolean DO_NOT_USE = false;
44     private static final int MODE_SHIFT = 30;
45     public static final int UNSPECIFIED = 0;
46     public static final int EXACTLY = 1 << MODE_SHIFT;
47     public static final int AT_MOST = 2 << MODE_SHIFT;
48 
49     public static final int MATCH_PARENT = -1;
50     public static final int WRAP_CONTENT = -2;
51     public static final int FIXED = -3;
52 
53     private final ArrayList<ConstraintWidget> mVariableDimensionsWidgets = new ArrayList<>();
54     private Measure mMeasure = new Measure();
55 
56     // @TODO: add description
updateHierarchy(ConstraintWidgetContainer layout)57     public void updateHierarchy(ConstraintWidgetContainer layout) {
58         mVariableDimensionsWidgets.clear();
59         final int childCount = layout.mChildren.size();
60         for (int i = 0; i < childCount; i++) {
61             ConstraintWidget widget = layout.mChildren.get(i);
62             if (widget.getHorizontalDimensionBehaviour()
63                     == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT
64                     || widget.getVerticalDimensionBehaviour()
65                     == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT) {
66                 mVariableDimensionsWidgets.add(widget);
67             }
68         }
69         layout.invalidateGraph();
70     }
71 
72     private ConstraintWidgetContainer mConstraintWidgetContainer;
73 
BasicMeasure(ConstraintWidgetContainer constraintWidgetContainer)74     public BasicMeasure(ConstraintWidgetContainer constraintWidgetContainer) {
75         this.mConstraintWidgetContainer = constraintWidgetContainer;
76     }
77 
measureChildren(ConstraintWidgetContainer layout)78     private void measureChildren(ConstraintWidgetContainer layout) {
79         final int childCount = layout.mChildren.size();
80         boolean optimize = layout.optimizeFor(Optimizer.OPTIMIZATION_GRAPH);
81         Measurer measurer = layout.getMeasurer();
82         for (int i = 0; i < childCount; i++) {
83             ConstraintWidget child = layout.mChildren.get(i);
84             if (child instanceof Guideline) {
85                 continue;
86             }
87             if (child instanceof Barrier) {
88                 continue;
89             }
90             if (child.isInVirtualLayout()) {
91                 continue;
92             }
93 
94             if (optimize && child.mHorizontalRun != null && child.mVerticalRun != null
95                     && child.mHorizontalRun.mDimension.resolved
96                     && child.mVerticalRun.mDimension.resolved) {
97                 continue;
98             }
99 
100             ConstraintWidget.DimensionBehaviour widthBehavior =
101                     child.getDimensionBehaviour(HORIZONTAL);
102             ConstraintWidget.DimensionBehaviour heightBehavior =
103                     child.getDimensionBehaviour(VERTICAL);
104 
105             boolean skip = widthBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT
106                     && child.mMatchConstraintDefaultWidth != MATCH_CONSTRAINT_WRAP
107                     && heightBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT
108                     && child.mMatchConstraintDefaultHeight != MATCH_CONSTRAINT_WRAP;
109 
110             if (!skip && layout.optimizeFor(Optimizer.OPTIMIZATION_DIRECT)
111                     && !(child instanceof VirtualLayout)) {
112                 if (widthBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT
113                         && child.mMatchConstraintDefaultWidth == MATCH_CONSTRAINT_SPREAD
114                         && heightBehavior != ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT
115                         && !child.isInHorizontalChain()) {
116                     skip = true;
117                 }
118 
119                 if (heightBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT
120                         && child.mMatchConstraintDefaultHeight == MATCH_CONSTRAINT_SPREAD
121                         && widthBehavior != ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT
122                         && !child.isInHorizontalChain()) {
123                     skip = true;
124                 }
125 
126                 // Don't measure yet -- let the direct solver have a shot at it.
127                 if ((widthBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT
128                         || heightBehavior == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT)
129                         && child.mDimensionRatio > 0) {
130                     skip = true;
131                 }
132             }
133 
134             if (skip) {
135                 // we don't need to measure here as the dimension of the widget
136                 // will be completely computed by the solver.
137                 continue;
138             }
139 
140             measure(measurer, child, Measure.SELF_DIMENSIONS);
141             if (layout.mMetrics != null) {
142                 layout.mMetrics.measuredWidgets++;
143             }
144         }
145         measurer.didMeasures();
146     }
147 
solveLinearSystem(ConstraintWidgetContainer layout, String reason, int pass, int w, int h)148     private void solveLinearSystem(ConstraintWidgetContainer layout,
149             String reason,
150             int pass,
151             int w,
152             int h) {
153         long startLayout = 0;
154         if (layout.mMetrics != null) {
155             startLayout = System.nanoTime();
156         }
157 
158         int minWidth = layout.getMinWidth();
159         int minHeight = layout.getMinHeight();
160         layout.setMinWidth(0);
161         layout.setMinHeight(0);
162         layout.setWidth(w);
163         layout.setHeight(h);
164         layout.setMinWidth(minWidth);
165         layout.setMinHeight(minHeight);
166         if (DEBUG) {
167             System.out.println("### Solve <" + reason + "> ###");
168         }
169         mConstraintWidgetContainer.setPass(pass);
170         mConstraintWidgetContainer.layout();
171         if (layout.mMetrics != null) {
172             long endLayout = System.nanoTime();
173             layout.mMetrics.mSolverPasses++;
174             layout.mMetrics.measuresLayoutDuration += (endLayout - startLayout);
175         }
176     }
177 
178     /**
179      * Called by ConstraintLayout onMeasure()
180      */
solverMeasure(ConstraintWidgetContainer layout, int optimizationLevel, int paddingX, int paddingY, int widthMode, int widthSize, int heightMode, int heightSize, int lastMeasureWidth, int lastMeasureHeight)181     public long solverMeasure(ConstraintWidgetContainer layout,
182             int optimizationLevel,
183             int paddingX, int paddingY,
184             int widthMode, int widthSize,
185             int heightMode, int heightSize,
186             int lastMeasureWidth,
187             int lastMeasureHeight) {
188         Measurer measurer = layout.getMeasurer();
189         long layoutTime = 0;
190 
191         final int childCount = layout.mChildren.size();
192         int startingWidth = layout.getWidth();
193         int startingHeight = layout.getHeight();
194 
195         boolean optimizeWrap =
196                 Optimizer.enabled(optimizationLevel, Optimizer.OPTIMIZATION_GRAPH_WRAP);
197         boolean optimize = optimizeWrap
198                 || Optimizer.enabled(optimizationLevel, Optimizer.OPTIMIZATION_GRAPH);
199 
200         if (optimize) {
201             for (int i = 0; i < childCount; i++) {
202                 ConstraintWidget child = layout.mChildren.get(i);
203                 boolean matchWidth = child.getHorizontalDimensionBehaviour()
204                         == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT;
205                 boolean matchHeight = child.getVerticalDimensionBehaviour()
206                         == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT;
207                 boolean ratio = matchWidth && matchHeight && child.getDimensionRatio() > 0;
208                 if (child.isInHorizontalChain() && ratio) {
209                     optimize = false;
210                     break;
211                 }
212                 if (child.isInVerticalChain() && ratio) {
213                     optimize = false;
214                     break;
215                 }
216                 if (child instanceof VirtualLayout) {
217                     optimize = false;
218                     break;
219                 }
220                 if (child.isInHorizontalChain()
221                         || child.isInVerticalChain()) {
222                     optimize = false;
223                     break;
224                 }
225             }
226         }
227 
228         if (optimize && LinearSystem.sMetrics != null) {
229             LinearSystem.sMetrics.measures++;
230         }
231 
232         boolean allSolved = false;
233 
234         optimize &= (widthMode == EXACTLY && heightMode == EXACTLY) || optimizeWrap;
235 
236         int computations = 0;
237 
238         if (optimize) {
239             // For non-optimizer this doesn't seem to be a problem.
240             // For both cases, having the width address max size early seems to work
241             //  (which makes sense).
242             // Putting it specific to optimizer to reduce unnecessary risk.
243             widthSize = Math.min(layout.getMaxWidth(), widthSize);
244             heightSize = Math.min(layout.getMaxHeight(), heightSize);
245 
246             if (widthMode == EXACTLY && layout.getWidth() != widthSize) {
247                 layout.setWidth(widthSize);
248                 layout.invalidateGraph();
249             }
250             if (heightMode == EXACTLY && layout.getHeight() != heightSize) {
251                 layout.setHeight(heightSize);
252                 layout.invalidateGraph();
253             }
254             if (widthMode == EXACTLY && heightMode == EXACTLY) {
255                 allSolved = layout.directMeasure(optimizeWrap);
256                 computations = 2;
257             } else {
258                 allSolved = layout.directMeasureSetup(optimizeWrap);
259                 if (widthMode == EXACTLY) {
260                     allSolved &= layout.directMeasureWithOrientation(optimizeWrap, HORIZONTAL);
261                     computations++;
262                 }
263                 if (heightMode == EXACTLY) {
264                     allSolved &= layout.directMeasureWithOrientation(optimizeWrap, VERTICAL);
265                     computations++;
266                 }
267             }
268             if (allSolved) {
269                 layout.updateFromRuns(widthMode == EXACTLY, heightMode == EXACTLY);
270             }
271         } else {
272             if (false) {
273                 layout.mHorizontalRun.clear();
274                 layout.mVerticalRun.clear();
275                 for (ConstraintWidget child : layout.getChildren()) {
276                     child.mHorizontalRun.clear();
277                     child.mVerticalRun.clear();
278                 }
279             }
280         }
281 
282         if (!allSolved || computations != 2) {
283             int optimizations = layout.getOptimizationLevel();
284             if (childCount > 0) {
285                 measureChildren(layout);
286             }
287             if (layout.mMetrics != null) {
288                 layoutTime = System.nanoTime();
289             }
290 
291             updateHierarchy(layout);
292 
293             // let's update the size dependent widgets if any...
294             final int sizeDependentWidgetsCount = mVariableDimensionsWidgets.size();
295 
296             // let's solve the linear system.
297             if (childCount > 0) {
298                 solveLinearSystem(layout, "First pass", 0, startingWidth, startingHeight);
299             }
300 
301             if (DEBUG) {
302                 System.out.println("size dependent widgets: " + sizeDependentWidgetsCount);
303             }
304 
305             if (sizeDependentWidgetsCount > 0) {
306                 boolean needSolverPass = false;
307                 boolean containerWrapWidth = layout.getHorizontalDimensionBehaviour()
308                         == ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
309                 boolean containerWrapHeight = layout.getVerticalDimensionBehaviour()
310                         == ConstraintWidget.DimensionBehaviour.WRAP_CONTENT;
311                 int minWidth = Math.max(layout.getWidth(),
312                         mConstraintWidgetContainer.getMinWidth());
313                 int minHeight = Math.max(layout.getHeight(),
314                         mConstraintWidgetContainer.getMinHeight());
315 
316                 ////////////////////////////////////////////////////////////////////////////////////
317                 // Let's first apply sizes for VirtualLayouts if any
318                 ////////////////////////////////////////////////////////////////////////////////////
319                 for (int i = 0; i < sizeDependentWidgetsCount; i++) {
320                     ConstraintWidget widget = mVariableDimensionsWidgets.get(i);
321                     if (!(widget instanceof VirtualLayout)) {
322                         continue;
323                     }
324                     int preWidth = widget.getWidth();
325                     int preHeight = widget.getHeight();
326                     needSolverPass |= measure(measurer, widget, Measure.TRY_GIVEN_DIMENSIONS);
327                     if (layout.mMetrics != null) {
328                         layout.mMetrics.measuredMatchWidgets++;
329                     }
330                     int measuredWidth = widget.getWidth();
331                     int measuredHeight = widget.getHeight();
332                     if (measuredWidth != preWidth) {
333                         widget.setWidth(measuredWidth);
334                         if (containerWrapWidth && widget.getRight() > minWidth) {
335                             int w = widget.getRight()
336                                     + widget.getAnchor(ConstraintAnchor.Type.RIGHT).getMargin();
337                             minWidth = Math.max(minWidth, w);
338                         }
339                         needSolverPass = true;
340                     }
341                     if (measuredHeight != preHeight) {
342                         widget.setHeight(measuredHeight);
343                         if (containerWrapHeight && widget.getBottom() > minHeight) {
344                             int h = widget.getBottom()
345                                     + widget.getAnchor(ConstraintAnchor.Type.BOTTOM).getMargin();
346                             minHeight = Math.max(minHeight, h);
347                         }
348                         needSolverPass = true;
349                     }
350                     VirtualLayout virtualLayout = (VirtualLayout) widget;
351                     needSolverPass |= virtualLayout.needSolverPass();
352                 }
353                 ////////////////////////////////////////////////////////////////////////////////////
354 
355                 int maxIterations = 2;
356                 for (int j = 0; j < maxIterations; j++) {
357                     for (int i = 0; i < sizeDependentWidgetsCount; i++) {
358                         ConstraintWidget widget = mVariableDimensionsWidgets.get(i);
359                         if ((widget instanceof Helper && !(widget instanceof VirtualLayout))
360                                 || widget instanceof Guideline) {
361                             continue;
362                         }
363                         if (widget.getVisibility() == GONE) {
364                             continue;
365                         }
366                         if (optimize && widget.mHorizontalRun.mDimension.resolved
367                                 && widget.mVerticalRun.mDimension.resolved) {
368                             continue;
369                         }
370                         if (widget instanceof VirtualLayout) {
371                             continue;
372                         }
373 
374                         int preWidth = widget.getWidth();
375                         int preHeight = widget.getHeight();
376                         int preBaselineDistance = widget.getBaselineDistance();
377 
378                         int measureStrategy = Measure.TRY_GIVEN_DIMENSIONS;
379                         if (j == maxIterations - 1) {
380                             measureStrategy = Measure.USE_GIVEN_DIMENSIONS;
381                         }
382                         boolean hasMeasure = measure(measurer, widget, measureStrategy);
383                         if (DO_NOT_USE && !widget.hasDependencies()) {
384                             hasMeasure = false;
385                         }
386                         needSolverPass |= hasMeasure;
387                         if (DEBUG && hasMeasure) {
388                             System.out.println("{#} Needs Solver pass as measure true for "
389                                     + widget.getDebugName());
390                         }
391                         if (layout.mMetrics != null) {
392                             layout.mMetrics.measuredMatchWidgets++;
393                         }
394 
395                         int measuredWidth = widget.getWidth();
396                         int measuredHeight = widget.getHeight();
397 
398                         if (measuredWidth != preWidth) {
399                             widget.setWidth(measuredWidth);
400                             if (containerWrapWidth && widget.getRight() > minWidth) {
401                                 int w = widget.getRight()
402                                         + widget.getAnchor(ConstraintAnchor.Type.RIGHT).getMargin();
403                                 minWidth = Math.max(minWidth, w);
404                             }
405                             if (DEBUG) {
406                                 System.out.println("{#} Needs Solver pass as Width for "
407                                         + widget.getDebugName() + " changed: "
408                                         + measuredWidth + " != " + preWidth);
409                             }
410                             needSolverPass = true;
411                         }
412                         if (measuredHeight != preHeight) {
413                             widget.setHeight(measuredHeight);
414                             if (containerWrapHeight && widget.getBottom() > minHeight) {
415                                 int h = widget.getBottom()
416                                         + widget.getAnchor(ConstraintAnchor.Type.BOTTOM)
417                                         .getMargin();
418                                 minHeight = Math.max(minHeight, h);
419                             }
420                             if (DEBUG) {
421                                 System.out.println("{#} Needs Solver pass as Height for "
422                                         + widget.getDebugName() + " changed: "
423                                         + measuredHeight + " != " + preHeight);
424                             }
425                             needSolverPass = true;
426                         }
427                         if (widget.hasBaseline()
428                                 && preBaselineDistance != widget.getBaselineDistance()) {
429                             if (DEBUG) {
430                                 System.out.println("{#} Needs Solver pass as Baseline for "
431                                         + widget.getDebugName() + " changed: "
432                                         + widget.getBaselineDistance() + " != "
433                                         + preBaselineDistance);
434                             }
435                             needSolverPass = true;
436                         }
437                     }
438                     if (needSolverPass) {
439                         solveLinearSystem(layout, "intermediate pass",
440                                 1 + j, startingWidth, startingHeight);
441                         needSolverPass = false;
442                     } else {
443                         break;
444                     }
445                 }
446             }
447             layout.setOptimizationLevel(optimizations);
448         }
449         if (layout.mMetrics != null) {
450             layoutTime = (System.nanoTime() - layoutTime);
451         }
452         return layoutTime;
453     }
454 
455     /**
456      * Convenience function to fill in the measure spec
457      *
458      * @param measurer        the measurer callback
459      * @param widget          the widget to measure
460      * @param measureStrategy how to use the current ConstraintWidget dimensions during the measure
461      * @return true if needs another solver pass
462      */
measure(Measurer measurer, ConstraintWidget widget, int measureStrategy)463     private boolean measure(Measurer measurer, ConstraintWidget widget, int measureStrategy) {
464         mMeasure.horizontalBehavior = widget.getHorizontalDimensionBehaviour();
465         mMeasure.verticalBehavior = widget.getVerticalDimensionBehaviour();
466         mMeasure.horizontalDimension = widget.getWidth();
467         mMeasure.verticalDimension = widget.getHeight();
468         mMeasure.measuredNeedsSolverPass = false;
469         mMeasure.measureStrategy = measureStrategy;
470 
471         boolean horizontalMatchConstraints = (mMeasure.horizontalBehavior
472                 == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT);
473         boolean verticalMatchConstraints = (mMeasure.verticalBehavior
474                 == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT);
475         boolean horizontalUseRatio = horizontalMatchConstraints && widget.mDimensionRatio > 0;
476         boolean verticalUseRatio = verticalMatchConstraints && widget.mDimensionRatio > 0;
477 
478         if (horizontalUseRatio) {
479             if (widget.mResolvedMatchConstraintDefault[HORIZONTAL]
480                     == ConstraintWidget.MATCH_CONSTRAINT_RATIO_RESOLVED) {
481                 mMeasure.horizontalBehavior = ConstraintWidget.DimensionBehaviour.FIXED;
482             }
483         }
484         if (verticalUseRatio) {
485             if (widget.mResolvedMatchConstraintDefault[VERTICAL]
486                     == ConstraintWidget.MATCH_CONSTRAINT_RATIO_RESOLVED) {
487                 mMeasure.verticalBehavior = ConstraintWidget.DimensionBehaviour.FIXED;
488             }
489         }
490 
491         measurer.measure(widget, mMeasure);
492         widget.setWidth(mMeasure.measuredWidth);
493         widget.setHeight(mMeasure.measuredHeight);
494         widget.setHasBaseline(mMeasure.measuredHasBaseline);
495         widget.setBaselineDistance(mMeasure.measuredBaseline);
496         mMeasure.measureStrategy = Measure.SELF_DIMENSIONS;
497         return mMeasure.measuredNeedsSolverPass;
498     }
499 
500     public interface Measurer {
501         // @TODO: add description
measure(ConstraintWidget widget, Measure measure)502         void measure(ConstraintWidget widget, Measure measure);
503 
504         // @TODO: add description
didMeasures()505         void didMeasures();
506     }
507 
508     public static class Measure {
509         public static int SELF_DIMENSIONS = 0;
510         public static int TRY_GIVEN_DIMENSIONS = 1;
511         public static int USE_GIVEN_DIMENSIONS = 2;
512         public ConstraintWidget.DimensionBehaviour horizontalBehavior;
513         public ConstraintWidget.DimensionBehaviour verticalBehavior;
514         public int horizontalDimension;
515         public int verticalDimension;
516         public int measuredWidth;
517         public int measuredHeight;
518         public int measuredBaseline;
519         public boolean measuredHasBaseline;
520         public boolean measuredNeedsSolverPass;
521         public int measureStrategy;
522     }
523 }
524