blob: 42b4efdbadb322619b2739557e33738b7836416b [file] [log] [blame]
Aurimas Liutikas88c7ff12023-08-10 12:42:26 -07001/*
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 */
16package com.android.systemui.complication;
17
18import android.annotation.IntDef;
19import android.view.ViewGroup;
20
21import java.lang.annotation.Retention;
22import java.lang.annotation.RetentionPolicy;
23import java.util.HashMap;
24import java.util.Map;
25import 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 */
32public 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<>();
97 INVALID_DIRECTIONS.put(POSITION_BOTTOM, DIRECTION_DOWN);
98 INVALID_DIRECTIONS.put(POSITION_TOP, DIRECTION_UP);
99 INVALID_DIRECTIONS.put(POSITION_START, DIRECTION_START);
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 */
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 */
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 */
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 */
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 */
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 */
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
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 */
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 */
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
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
287 public int getDirection() {
288 return mDirection;
289 }
290
291 @Position
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 */
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 */
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 */
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 */
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 */
332 public boolean snapsToGuide() {
333 return mSnapToGuide;
334 }
335}