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 package com.android.car.scalableui.model; 17 18 import static android.view.Display.DEFAULT_DISPLAY; 19 20 import android.animation.Animator; 21 22 import androidx.annotation.NonNull; 23 import androidx.annotation.Nullable; 24 25 import java.util.ArrayList; 26 import java.util.List; 27 import java.util.stream.Collectors; 28 29 /** 30 * Represents the state of a panel in the Scalable UI system. 31 * 32 * <p>A PanelState defines the different variants (layouts) that a panel can have, as well as the 33 * transitions between those variants. It also manages the current variant and any running 34 * animations. 35 */ 36 public class PanelState { 37 private static final String TAG = PanelState.class.getSimpleName(); 38 39 public static final String DEFAULT_ROLE = "DEFAULT"; 40 public static final String DECOR_PANEL_ID_PREFIX = "decor"; 41 42 private String mDefaultVariant; 43 private int mDisplayId; 44 45 private final String mId; 46 private final Role mRole; 47 private final List<Variant> mVariants = new ArrayList<>(); 48 private final List<Transition> mTransitions = new ArrayList<>(); 49 50 @Nullable private Animator mRunningAnimator; 51 @Nullable private Variant mCurrentVariant; 52 53 /** 54 * Constructor for PanelState. 55 * 56 * @param id The ID of the panel. 57 * @param role The role of the panel. 58 */ PanelState(@onNull String id, @NonNull Role role)59 public PanelState(@NonNull String id, @NonNull Role role) { 60 mId = id; 61 mRole = role; 62 mDisplayId = DEFAULT_DISPLAY; 63 } 64 65 /** 66 * Constructor to copy a PanelState 67 */ PanelState(@onNull PanelState other)68 public PanelState(@NonNull PanelState other) { 69 mId = other.mId; 70 mRole = other.mRole; 71 mDisplayId = other.mDisplayId; 72 mDefaultVariant = other.mDefaultVariant; 73 mVariants.addAll(other.mVariants); 74 mTransitions.addAll(other.mTransitions); 75 mRunningAnimator = other.mRunningAnimator; 76 mCurrentVariant = other.mCurrentVariant; 77 } 78 79 /** Returns id */ 80 @NonNull getId()81 public String getId() { 82 return mId; 83 } 84 85 /** Adds variant */ addVariant(@onNull Variant variant)86 public void addVariant(@NonNull Variant variant) { 87 mVariants.add(variant); 88 } 89 90 /** Adds transition */ addTransition(@onNull Transition transition)91 public void addTransition(@NonNull Transition transition) { 92 mTransitions.add(transition); 93 } 94 95 /** Returns current variant */ 96 @Nullable getCurrentVariant()97 public Variant getCurrentVariant() { 98 if (mCurrentVariant == null) { 99 // Ensure mVariants is not empty before accessing 100 if (!mVariants.isEmpty()) { 101 mCurrentVariant = mVariants.get(0); 102 } 103 } 104 return mCurrentVariant; 105 } 106 107 /** Returns variant with the given id */ 108 @Nullable getVariant(@onNull String id)109 public Variant getVariant(@NonNull String id) { 110 for (Variant variant : mVariants) { 111 if (variant.getId().equals(id)) { 112 return variant; 113 } 114 } 115 return null; 116 } 117 118 /** Sets variant with the given id */ setVariant(@onNull String id)119 public void setVariant(@NonNull String id) { 120 setVariant(id, null); 121 } 122 123 /** Resets to the default variant */ resetVariant()124 public void resetVariant() { 125 setVariant(mDefaultVariant); 126 } 127 128 /** 129 * Sets variant 130 * 131 * @param id The ID of the variant to set. 132 * @param event The event that triggered the variant change. 133 */ setVariant(@onNull String id, @Nullable Event event)134 public void setVariant(@NonNull String id, @Nullable Event event) { 135 for (Variant variant : mVariants) { 136 if (variant != null && variant.getId().equals(id)) { 137 mCurrentVariant = variant; 138 if (event != null) { 139 mCurrentVariant.updateFromEvent(event); 140 } 141 return; 142 } 143 } 144 } 145 146 /** Returns the role */ 147 @NonNull getRole()148 public Role getRole() { 149 return mRole; 150 } 151 152 /** Returns true if animating */ isAnimating()153 public boolean isAnimating() { 154 return mRunningAnimator != null && mRunningAnimator.isRunning(); 155 } 156 157 /** Called on animation start */ onAnimationStart(@onNull Animator animator)158 public void onAnimationStart(@NonNull Animator animator) { 159 if (mRunningAnimator != null) { 160 mRunningAnimator.pause(); 161 mRunningAnimator.removeAllListeners(); 162 } 163 mRunningAnimator = animator; 164 } 165 166 /** Called on animation end */ onAnimationEnd()167 public void onAnimationEnd() { 168 mRunningAnimator = null; 169 } 170 171 /** Returns transition for the given event */ 172 @Nullable getTransition(@ullable Event event)173 public Transition getTransition(@Nullable Event event) { 174 if (event == null) { 175 return null; 176 } 177 // If both onEvent and fromVariant matches 178 String currentVariantId = 179 (getCurrentVariant() != null) ? getCurrentVariant().getId() : null; 180 Transition result = getTransitionInternal(event, currentVariantId); 181 182 if (result != null) { 183 return result; 184 } 185 // If only onEvent matches 186 return getTransitionInternal(event); 187 } 188 189 @Nullable getTransitionInternal(@onNull Event event, @Nullable String fromVariant)190 private Transition getTransitionInternal(@NonNull Event event, @Nullable String fromVariant) { 191 for (Transition transition : mTransitions) { 192 if (event.isMatch(transition.getOnEvent()) 193 && transition.getFromVariant() != null 194 && transition.getFromVariant().getId().equals(fromVariant)) { 195 return transition; 196 } 197 } 198 return null; 199 } 200 201 @Nullable getTransitionInternal(@onNull Event event)202 private Transition getTransitionInternal(@NonNull Event event) { 203 for (Transition transition : mTransitions) { 204 if (event.isMatch(transition.getOnEvent()) && transition.getFromVariant() == null) { 205 return transition; 206 } 207 } 208 return null; 209 } 210 211 /** 212 * Returns the ID of the display the panel is associated with. 213 * 214 * @return The display ID. 215 */ getDisplayId()216 public int getDisplayId() { 217 return mDisplayId; 218 } 219 setDefaultVariant(@ullable String defaultVariant)220 void setDefaultVariant(@Nullable String defaultVariant) { 221 mDefaultVariant = defaultVariant; 222 } 223 setDisplayId(int displayId)224 void setDisplayId(int displayId) { 225 mDisplayId = displayId; 226 } 227 setVariants(@onNull List<Variant> variants)228 void setVariants(@NonNull List<Variant> variants) { 229 mVariants.clear(); 230 mVariants.addAll(variants); 231 } 232 setTransitions(@onNull List<Transition> transitions)233 void setTransitions(@NonNull List<Transition> transitions) { 234 mTransitions.clear(); 235 mTransitions.addAll(transitions); 236 } 237 238 @Override 239 @NonNull toString()240 public String toString() { 241 return "PanelState{" 242 + "mId='" + mId + '\'' 243 + ", mRole=" + mRole 244 + ", mDefaultVariant='" + mDefaultVariant + '\'' 245 + ", mDisplayId=" + mDisplayId 246 + ", mVariants=" + mVariants.stream() 247 .map(Variant::toString) 248 .collect(Collectors.joining(", ", "[", "]")) 249 + ", mTransitions=" + mTransitions.stream() 250 .map(Transition::toString) 251 .collect(Collectors.joining(", ", "[", "]")) 252 + ", mRunningAnimator=" + mRunningAnimator 253 + ", mCurrentVariant=" 254 + (mCurrentVariant != null ? mCurrentVariant.getId() : "null") 255 + '}'; 256 } 257 258 /** Builder for {@link PanelState} objects. */ 259 public static class Builder { 260 private String mId; 261 private Role mRole; 262 private String mDefaultVariant; 263 private Integer mDisplayId; 264 private List<Variant> mVariants = new ArrayList<>(); 265 private List<Transition> mTransitions = new ArrayList<>(); 266 Builder(@onNull String id, @NonNull Role role)267 public Builder(@NonNull String id, @NonNull Role role) { 268 mId = id; 269 mRole = role; 270 } 271 272 /** Sets default variant */ setDefaultVariant(@ullable String defaultVariant)273 public Builder setDefaultVariant(@Nullable String defaultVariant) { 274 mDefaultVariant = defaultVariant; 275 return this; 276 } 277 278 /** Sets display id */ setDisplayId(int displayId)279 public Builder setDisplayId(int displayId) { 280 mDisplayId = displayId; 281 return this; 282 } 283 284 /** Adds a variant */ addVariant(@onNull Variant variant)285 public Builder addVariant(@NonNull Variant variant) { 286 mVariants.add(variant); 287 return this; 288 } 289 290 /** Adds a transitions */ addTransition(@onNull Transition transition)291 public Builder addTransition(@NonNull Transition transition) { 292 mTransitions.add(transition); 293 return this; 294 } 295 296 /** Sets variants */ setVariants(@onNull List<Variant> variants)297 public Builder setVariants(@NonNull List<Variant> variants) { 298 mVariants = new ArrayList<>(variants); // Defensive copy 299 return this; 300 } 301 302 /** Sets transitions */ setTransitions(@onNull List<Transition> transitions)303 public Builder setTransitions(@NonNull List<Transition> transitions) { 304 mTransitions = new ArrayList<>(transitions); // Defensive copy 305 return this; 306 } 307 308 /** Returns the {@link PanelState} instance */ 309 @NonNull build()310 public PanelState build() { 311 PanelState panelState = new PanelState(mId, mRole); 312 panelState.setDefaultVariant(mDefaultVariant); 313 if (mDisplayId != null) { 314 panelState.setDisplayId(mDisplayId); 315 } 316 panelState.setVariants(mVariants); 317 panelState.setTransitions(mTransitions); 318 return panelState; 319 } 320 } 321 } 322