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