1 /*
2  * Copyright (C) 2022 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 package com.android.systemui.complication;
17 
18 import android.annotation.IntDef;
19 import android.view.ViewGroup;
20 
21 import java.lang.annotation.Retention;
22 import java.lang.annotation.RetentionPolicy;
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.function.Consumer;
26 
27 /**
28  * {@link ComplicationLayoutParams} allows a {@link Complication} to express its preferred location
29  * and dimensions. Note that these parameters are not directly applied by any {@link ViewGroup}.
30  * They are instead consulted for the final parameters which best seem fit for usage.
31  */
32 public class ComplicationLayoutParams extends ViewGroup.LayoutParams {
33     @Retention(RetentionPolicy.SOURCE)
34     @IntDef(flag = true, prefix = { "POSITION_" }, value = {
35             POSITION_TOP,
36             POSITION_END,
37             POSITION_BOTTOM,
38             POSITION_START,
39     })
40 
41     public @interface Position {}
42     /** Align view with the top of parent or bottom of preceding {@link Complication}. */
43     public static final int POSITION_TOP = 1 << 0;
44     /** Align view with the bottom of parent or top of preceding {@link Complication}. */
45     public static final int POSITION_BOTTOM = 1 << 1;
46     /** Align view with the start of parent or end of preceding {@link Complication}. */
47     public static final int POSITION_START = 1 << 2;
48     /** Align view with the end of parent or start of preceding {@link Complication}. */
49     public static final int POSITION_END = 1 << 3;
50 
51     private static final int FIRST_POSITION = POSITION_TOP;
52     private static final int LAST_POSITION = POSITION_END;
53 
54     private static final int DIRECTIONAL_SPACING_UNSPECIFIED = 0xFFFFFFFF;
55     private static final int CONSTRAINT_UNSPECIFIED = 0xFFFFFFFF;
56 
57     @Retention(RetentionPolicy.SOURCE)
58     @IntDef(flag = true, prefix = { "DIRECTION_" }, value = {
59             DIRECTION_UP,
60             DIRECTION_DOWN,
61             DIRECTION_START,
62             DIRECTION_END,
63     })
64 
65     @interface Direction {}
66     /** Position view upward from position. */
67     public static final int DIRECTION_UP = 1 << 0;
68     /** Position view downward from position. */
69     public static final int DIRECTION_DOWN = 1 << 1;
70     /** Position view towards the start of the parent. */
71     public static final int DIRECTION_START = 1 << 2;
72     /** Position view towards the end of parent. */
73     public static final int DIRECTION_END = 1 << 3;
74 
75     @Position
76     private final int mPosition;
77 
78     @Direction
79     private final int mDirection;
80 
81     private final int mWeight;
82 
83     private final int mDirectionalSpacing;
84 
85     private final int mConstraint;
86 
87     private final boolean mSnapToGuide;
88 
89     // Do not allow specifying opposite positions
90     private static final int[] INVALID_POSITIONS =
91             { POSITION_BOTTOM | POSITION_TOP, POSITION_END | POSITION_START };
92 
93     // Do not allow for specifying a direction towards the outside of the container.
94     private static final Map<Integer, Integer> INVALID_DIRECTIONS;
95     static {
96         INVALID_DIRECTIONS = new HashMap<>();
INVALID_DIRECTIONS.put(POSITION_BOTTOM, DIRECTION_DOWN)97         INVALID_DIRECTIONS.put(POSITION_BOTTOM, DIRECTION_DOWN);
INVALID_DIRECTIONS.put(POSITION_TOP, DIRECTION_UP)98         INVALID_DIRECTIONS.put(POSITION_TOP, DIRECTION_UP);
INVALID_DIRECTIONS.put(POSITION_START, DIRECTION_START)99         INVALID_DIRECTIONS.put(POSITION_START, DIRECTION_START);
INVALID_DIRECTIONS.put(POSITION_END, DIRECTION_END)100         INVALID_DIRECTIONS.put(POSITION_END, DIRECTION_END);
101     }
102 
103     /**
104      * Constructs a {@link ComplicationLayoutParams}.
105      * @param width The width {@link android.view.View.MeasureSpec} for the view.
106      * @param height The height {@link android.view.View.MeasureSpec} for the view.
107      * @param position The place within the parent container where the view should be positioned.
108      * @param direction The direction the view should be laid out from either the parent container
109      *                  or preceding view.
110      * @param weight The weight that should be considered for this view when compared to other
111      *               views. This has an impact on the placement of the view but not the rendering of
112      *               the view.
113      */
ComplicationLayoutParams(int width, int height, @Position int position, @Direction int direction, int weight)114     public ComplicationLayoutParams(int width, int height, @Position int position,
115             @Direction int direction, int weight) {
116         this(width, height, position, direction, weight, DIRECTIONAL_SPACING_UNSPECIFIED,
117                 CONSTRAINT_UNSPECIFIED, false);
118     }
119 
120     /**
121      * Constructs a {@link ComplicationLayoutParams}.
122      * @param width The width {@link android.view.View.MeasureSpec} for the view.
123      * @param height The height {@link android.view.View.MeasureSpec} for the view.
124      * @param position The place within the parent container where the view should be positioned.
125      * @param direction The direction the view should be laid out from either the parent container
126      *                  or preceding view.
127      * @param weight The weight that should be considered for this view when compared to other
128      *               views. This has an impact on the placement of the view but not the rendering of
129      *               the view.
130      * @param directionalSpacing The spacing to apply between complications.
131      */
ComplicationLayoutParams(int width, int height, @Position int position, @Direction int direction, int weight, int directionalSpacing)132     public ComplicationLayoutParams(int width, int height, @Position int position,
133             @Direction int direction, int weight, int directionalSpacing) {
134         this(width, height, position, direction, weight, directionalSpacing, CONSTRAINT_UNSPECIFIED,
135                 false);
136     }
137 
138     /**
139      * Constructs a {@link ComplicationLayoutParams}.
140      * @param width The width {@link android.view.View.MeasureSpec} for the view.
141      * @param height The height {@link android.view.View.MeasureSpec} for the view.
142      * @param position The place within the parent container where the view should be positioned.
143      * @param direction The direction the view should be laid out from either the parent container
144      *                  or preceding view.
145      * @param weight The weight that should be considered for this view when compared to other
146      *               views. This has an impact on the placement of the view but not the rendering of
147      *               the view.
148      * @param directionalSpacing The spacing to apply between complications.
149      * @param constraint The max width or height the complication is allowed to spread, depending on
150      *                   its direction. For horizontal directions, this would be applied on width,
151      *                   and for vertical directions, height.
152      */
ComplicationLayoutParams(int width, int height, @Position int position, @Direction int direction, int weight, int directionalSpacing, int constraint)153     public ComplicationLayoutParams(int width, int height, @Position int position,
154             @Direction int direction, int weight, int directionalSpacing, int constraint) {
155         this(width, height, position, direction, weight, directionalSpacing, constraint, false);
156     }
157 
158     /**
159      * Constructs a {@link ComplicationLayoutParams}.
160      * @param width The width {@link android.view.View.MeasureSpec} for the view.
161      * @param height The height {@link android.view.View.MeasureSpec} for the view.
162      * @param position The place within the parent container where the view should be positioned.
163      * @param direction The direction the view should be laid out from either the parent container
164      *                  or preceding view.
165      * @param weight The weight that should be considered for this view when compared to other
166      *               views. This has an impact on the placement of the view but not the rendering of
167      *               the view.
168      * @param snapToGuide When set to {@code true}, the dimension perpendicular to the direction
169      *                    will be automatically set to align with a predetermined guide for that
170      *                    side. For example, if the complication is aligned to the top end and
171      *                    direction is down, then the width of the complication will be set to span
172      *                    from the end of the parent to the guide.
173      */
ComplicationLayoutParams(int width, int height, @Position int position, @Direction int direction, int weight, boolean snapToGuide)174     public ComplicationLayoutParams(int width, int height, @Position int position,
175             @Direction int direction, int weight, boolean snapToGuide) {
176         this(width, height, position, direction, weight, DIRECTIONAL_SPACING_UNSPECIFIED,
177                 CONSTRAINT_UNSPECIFIED, snapToGuide);
178     }
179 
180     /**
181      * Constructs a {@link ComplicationLayoutParams}.
182      * @param width The width {@link android.view.View.MeasureSpec} for the view.
183      * @param height The height {@link android.view.View.MeasureSpec} for the view.
184      * @param position The place within the parent container where the view should be positioned.
185      * @param direction The direction the view should be laid out from either the parent container
186      *                  or preceding view.
187      * @param weight The weight that should be considered for this view when compared to other
188      *               views. This has an impact on the placement of the view but not the rendering of
189      *               the view.
190      * @param directionalSpacing The spacing to apply between complications.
191      * @param constraint The max width or height the complication is allowed to spread, depending on
192      *                   its direction. For horizontal directions, this would be applied on width,
193      *                   and for vertical directions, height.
194      * @param snapToGuide When set to {@code true}, the dimension perpendicular to the direction
195      *                    will be automatically set to align with a predetermined guide for that
196      *                    side. For example, if the complication is aligned to the top end and
197      *                    direction is down, then the width of the complication will be set to span
198      *                    from the end of the parent to the guide.
199      */
ComplicationLayoutParams(int width, int height, @Position int position, @Direction int direction, int weight, int directionalSpacing, int constraint, boolean snapToGuide)200     public ComplicationLayoutParams(int width, int height, @Position int position,
201             @Direction int direction, int weight, int directionalSpacing, int constraint,
202             boolean snapToGuide) {
203         super(width, height);
204 
205         if (!validatePosition(position)) {
206             throw new IllegalArgumentException("invalid position:" + position);
207         }
208         mPosition = position;
209 
210         if (!validateDirection(position, direction)) {
211             throw new IllegalArgumentException("invalid direction:" + direction);
212         }
213 
214         mDirection = direction;
215 
216         mWeight = weight;
217 
218         mDirectionalSpacing = directionalSpacing;
219 
220         mConstraint = constraint;
221 
222         mSnapToGuide = snapToGuide;
223     }
224 
225     /**
226      * Constructs {@link ComplicationLayoutParams} from an existing instance.
227      */
ComplicationLayoutParams(ComplicationLayoutParams source)228     public ComplicationLayoutParams(ComplicationLayoutParams source) {
229         super(source);
230         mPosition = source.mPosition;
231         mDirection = source.mDirection;
232         mWeight = source.mWeight;
233         mDirectionalSpacing = source.mDirectionalSpacing;
234         mConstraint = source.mConstraint;
235         mSnapToGuide = source.mSnapToGuide;
236     }
237 
validateDirection(@osition int position, @Direction int direction)238     private static boolean validateDirection(@Position int position, @Direction int direction) {
239         for (int currentPosition = FIRST_POSITION; currentPosition <= LAST_POSITION;
240                 currentPosition <<= 1) {
241             if ((position & currentPosition) == currentPosition
242                     && INVALID_DIRECTIONS.containsKey(currentPosition)
243                     && (direction & INVALID_DIRECTIONS.get(currentPosition)) != 0) {
244                 return false;
245             }
246         }
247 
248         return true;
249     }
250 
251     /**
252      * Iterates over the defined positions and invokes the specified {@link Consumer} for each
253      * position specified for this {@link ComplicationLayoutParams}.
254      */
iteratePositions(Consumer<Integer> consumer)255     public void iteratePositions(Consumer<Integer> consumer) {
256         iteratePositions(consumer, mPosition);
257     }
258 
259     /**
260      * Iterates over the defined positions and invokes the specified {@link Consumer} for each
261      * position specified by the given {@code position}.
262      */
iteratePositions(Consumer<Integer> consumer, @Position int position)263     public static void iteratePositions(Consumer<Integer> consumer, @Position int position) {
264         for (int currentPosition = FIRST_POSITION; currentPosition <= LAST_POSITION;
265                 currentPosition <<= 1) {
266             if ((position & currentPosition) == currentPosition) {
267                 consumer.accept(currentPosition);
268             }
269         }
270     }
271 
validatePosition(@osition int position)272     private static boolean validatePosition(@Position int position) {
273         if (position == 0) {
274             return false;
275         }
276 
277         for (int combination : INVALID_POSITIONS) {
278             if ((position & combination) == combination) {
279                 return false;
280             }
281         }
282 
283         return true;
284     }
285 
286     @Direction
getDirection()287     public int getDirection() {
288         return mDirection;
289     }
290 
291     @Position
getPosition()292     public int getPosition() {
293         return mPosition;
294     }
295 
296     /**
297      * Returns the set weight for the complication. The weight determines ordering a complication
298      * given the same position/direction.
299      */
getWeight()300     public int getWeight() {
301         return mWeight;
302     }
303 
304     /**
305      * Returns the spacing to apply between complications, or the given default if no spacing is
306      * specified.
307      */
getDirectionalSpacing(int defaultSpacing)308     public int getDirectionalSpacing(int defaultSpacing) {
309         return mDirectionalSpacing == DIRECTIONAL_SPACING_UNSPECIFIED
310                 ? defaultSpacing : mDirectionalSpacing;
311     }
312 
313     /**
314      * Returns whether the horizontal or vertical constraint has been specified.
315      */
constraintSpecified()316     public boolean constraintSpecified() {
317         return mConstraint != CONSTRAINT_UNSPECIFIED;
318     }
319 
320     /**
321      * Returns the horizontal or vertical constraint of the complication, depending its direction.
322      * For horizontal directions, this is the max width, and for vertical directions, max height.
323      */
getConstraint()324     public int getConstraint() {
325         return mConstraint;
326     }
327 
328     /**
329      * Returns whether the complication's dimension perpendicular to direction should be
330      * automatically set.
331      */
snapsToGuide()332     public boolean snapsToGuide() {
333         return mSnapToGuide;
334     }
335 }
336