1 /* 2 * Copyright (C) 2021 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 com.android.server.display.layout; 18 19 import static android.view.Display.DEFAULT_DISPLAY; 20 21 import static com.android.server.display.layout.Layout.Display.POSITION_UNKNOWN; 22 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.text.TextUtils; 26 import android.util.ArraySet; 27 import android.util.Slog; 28 import android.view.DisplayAddress; 29 30 import java.util.ArrayList; 31 import java.util.List; 32 import java.util.Objects; 33 34 /** 35 * Holds a collection of {@link Display}s. A single instance of this class describes 36 * how to organize one or more DisplayDevices into LogicalDisplays for a particular device 37 * state. For example, there may be one instance of this class to describe display layout when 38 * a foldable device is folded, and a second instance for when the device is unfolded. 39 */ 40 public class Layout { 41 public static final String DEFAULT_DISPLAY_GROUP_NAME = ""; 42 43 private static final String TAG = "Layout"; 44 45 // Lead display Id is set to this if this is not a follower display, and therefore 46 // has no lead. 47 public static final int NO_LEAD_DISPLAY = -1; 48 49 private final List<Display> mDisplays = new ArrayList<>(2); 50 51 @Override toString()52 public String toString() { 53 return mDisplays.toString(); 54 } 55 56 @Override equals(Object obj)57 public boolean equals(Object obj) { 58 59 if (!(obj instanceof Layout)) { 60 return false; 61 } 62 63 Layout otherLayout = (Layout) obj; 64 return this.mDisplays.equals(otherLayout.mDisplays); 65 } 66 67 @Override hashCode()68 public int hashCode() { 69 return mDisplays.hashCode(); 70 } 71 72 /** 73 * Creates the default 1:1 LogicalDisplay mapping for the specified DisplayDevice. 74 * 75 * @param address Address of the device. 76 * @param idProducer Produces the logical display id. 77 */ createDefaultDisplayLocked(@onNull DisplayAddress address, DisplayIdProducer idProducer)78 public void createDefaultDisplayLocked(@NonNull DisplayAddress address, 79 DisplayIdProducer idProducer) { 80 createDisplayLocked(address, /* isDefault= */ true, /* isEnabled= */ true, 81 DEFAULT_DISPLAY_GROUP_NAME, idProducer, POSITION_UNKNOWN, 82 /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null, 83 /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null); 84 } 85 86 /** 87 * Creates a simple 1:1 LogicalDisplay mapping for the specified DisplayDevice. 88 * 89 * @param address Address of the device. 90 * @param isDefault Indicates if the device is meant to be the default display. 91 * @param isEnabled Indicates if this display is usable and can be switched on 92 * @param displayGroupName Name of the display group to which the display is assigned. 93 * @param idProducer Produces the logical display id. 94 * @param position Indicates the position this display is facing in this layout. 95 * @param leadDisplayAddress Address of a display that this one follows ({@code null} if none). 96 * @param brightnessThrottlingMapId Name of which brightness throttling policy should be used. 97 * @param refreshRateZoneId Layout limited refresh rate zone name. 98 * @param refreshRateThermalThrottlingMapId Name of which refresh rate throttling 99 * policy should be used. 100 * 101 * @exception IllegalArgumentException When a default display owns a display group other than 102 * DEFAULT_DISPLAY_GROUP. 103 */ createDisplayLocked( @onNull DisplayAddress address, boolean isDefault, boolean isEnabled, String displayGroupName, DisplayIdProducer idProducer, int position, @Nullable DisplayAddress leadDisplayAddress, String brightnessThrottlingMapId, @Nullable String refreshRateZoneId, @Nullable String refreshRateThermalThrottlingMapId)104 public void createDisplayLocked( 105 @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled, 106 String displayGroupName, DisplayIdProducer idProducer, int position, 107 @Nullable DisplayAddress leadDisplayAddress, String brightnessThrottlingMapId, 108 @Nullable String refreshRateZoneId, 109 @Nullable String refreshRateThermalThrottlingMapId) { 110 if (contains(address)) { 111 Slog.w(TAG, "Attempting to add second definition for display-device: " + address); 112 return; 113 } 114 115 // See if we're dealing with the "default" display 116 if (isDefault && getById(DEFAULT_DISPLAY) != null) { 117 Slog.w(TAG, "Ignoring attempt to add a second default display: " + address); 118 return; 119 } 120 121 if (displayGroupName == null) { 122 displayGroupName = DEFAULT_DISPLAY_GROUP_NAME; 123 } 124 if (isDefault && !displayGroupName.equals(DEFAULT_DISPLAY_GROUP_NAME)) { 125 throw new IllegalArgumentException("Default display should own DEFAULT_DISPLAY_GROUP"); 126 } 127 if (isDefault && leadDisplayAddress != null) { 128 throw new IllegalArgumentException("Default display cannot have a lead display"); 129 } 130 if (address.equals(leadDisplayAddress)) { 131 throw new IllegalArgumentException("Lead display address cannot be the same as display " 132 + " address"); 133 } 134 // Assign a logical display ID and create the new display. 135 // Note that the logical display ID is saved into the layout, so when switching between 136 // different layouts, a logical display can be destroyed and later recreated with the 137 // same logical display ID. 138 final int logicalDisplayId = idProducer.getId(isDefault); 139 140 final Display display = new Display(address, logicalDisplayId, isEnabled, displayGroupName, 141 brightnessThrottlingMapId, position, leadDisplayAddress, refreshRateZoneId, 142 refreshRateThermalThrottlingMapId); 143 144 mDisplays.add(display); 145 } 146 147 /** 148 * @param id The ID of the display to remove. 149 */ removeDisplayLocked(int id)150 public void removeDisplayLocked(int id) { 151 Display display = getById(id); 152 if (display != null) { 153 mDisplays.remove(display); 154 } 155 } 156 157 /** 158 * Applies post-processing to displays to make sure the information of each display is 159 * up-to-date. 160 * 161 * <p>At creation of a display, lead display is specified by display address. At post 162 * processing, we convert it to logical display ID. 163 */ postProcessLocked()164 public void postProcessLocked() { 165 for (int i = 0; i < mDisplays.size(); i++) { 166 Display display = mDisplays.get(i); 167 if (display.getLogicalDisplayId() == DEFAULT_DISPLAY) { 168 display.setLeadDisplayId(NO_LEAD_DISPLAY); 169 continue; 170 } 171 DisplayAddress leadDisplayAddress = display.getLeadDisplayAddress(); 172 if (leadDisplayAddress == null) { 173 display.setLeadDisplayId(NO_LEAD_DISPLAY); 174 continue; 175 } 176 Display leadDisplay = getByAddress(leadDisplayAddress); 177 if (leadDisplay == null) { 178 throw new IllegalArgumentException("Cannot find a lead display whose address is " 179 + leadDisplayAddress); 180 } 181 if (!TextUtils.equals(display.getDisplayGroupName(), 182 leadDisplay.getDisplayGroupName())) { 183 throw new IllegalArgumentException("Lead display(" + leadDisplay + ") should be in " 184 + "the same display group of the display(" + display + ")"); 185 } 186 if (hasCyclicLeadDisplay(display)) { 187 throw new IllegalArgumentException("Display(" + display + ") has a cyclic lead " 188 + "display"); 189 } 190 display.setLeadDisplayId(leadDisplay.getLogicalDisplayId()); 191 } 192 } 193 194 /** 195 * @param address The address to check. 196 * 197 * @return True if the specified address is used in this layout. 198 */ contains(@onNull DisplayAddress address)199 public boolean contains(@NonNull DisplayAddress address) { 200 final int size = mDisplays.size(); 201 for (int i = 0; i < size; i++) { 202 if (address.equals(mDisplays.get(i).getAddress())) { 203 return true; 204 } 205 } 206 return false; 207 } 208 209 /** 210 * @param id The display ID to check. 211 * 212 * @return The display corresponding to the specified display ID. 213 */ 214 @Nullable getById(int id)215 public Display getById(int id) { 216 for (int i = 0; i < mDisplays.size(); i++) { 217 Display display = mDisplays.get(i); 218 if (id == display.getLogicalDisplayId()) { 219 return display; 220 } 221 } 222 return null; 223 } 224 225 /** 226 * @param address The display address to check. 227 * 228 * @return The display corresponding to the specified address. 229 */ 230 @Nullable getByAddress(@onNull DisplayAddress address)231 public Display getByAddress(@NonNull DisplayAddress address) { 232 for (int i = 0; i < mDisplays.size(); i++) { 233 Display display = mDisplays.get(i); 234 if (address.equals(display.getAddress())) { 235 return display; 236 } 237 } 238 return null; 239 } 240 241 /** 242 * @param index The index of the display to return. 243 * 244 * @return the display at the specified index. 245 */ getAt(int index)246 public Display getAt(int index) { 247 return mDisplays.get(index); 248 } 249 250 /** 251 * @return The number of displays defined for this layout. 252 */ size()253 public int size() { 254 return mDisplays.size(); 255 } 256 hasCyclicLeadDisplay(Display display)257 private boolean hasCyclicLeadDisplay(Display display) { 258 ArraySet<Display> visited = new ArraySet<>(); 259 260 while (display != null) { 261 if (visited.contains(display)) { 262 return true; 263 } 264 visited.add(display); 265 DisplayAddress leadDisplayAddress = display.getLeadDisplayAddress(); 266 display = leadDisplayAddress == null ? null : getByAddress(leadDisplayAddress); 267 } 268 return false; 269 } 270 271 /** 272 * Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s. 273 */ 274 public static class Display { 275 public static final int POSITION_UNKNOWN = -1; 276 public static final int POSITION_FRONT = 0; 277 public static final int POSITION_REAR = 1; 278 279 // Address of the display device to map to this display. 280 private final DisplayAddress mAddress; 281 282 // Logical Display ID to apply to this display. 283 private final int mLogicalDisplayId; 284 285 // Indicates if this display is usable and can be switched on 286 private final boolean mIsEnabled; 287 288 // Name of display group to which the display is assigned 289 private final String mDisplayGroupName; 290 291 // The direction the display faces 292 // {@link DeviceStateToLayoutMap.POSITION_FRONT} or 293 // {@link DeviceStateToLayoutMap.POSITION_REAR}. 294 // {@link DeviceStateToLayoutMap.POSITION_UNKNOWN} is unspecified. 295 private final int mPosition; 296 297 // The ID of the thermal brightness throttling map that should be used. This can change 298 // e.g. in concurrent displays mode in which a stricter brightness throttling policy might 299 // need to be used. 300 @Nullable 301 private final String mThermalBrightnessThrottlingMapId; 302 303 // The address of the lead display that is specified in display-layout-configuration. 304 @Nullable 305 private final DisplayAddress mLeadDisplayAddress; 306 307 // Refresh rate zone id for specific layout 308 @Nullable 309 private final String mRefreshRateZoneId; 310 311 @Nullable 312 private final String mThermalRefreshRateThrottlingMapId; 313 314 // The ID of the lead display that this display will follow in a layout. -1 means no lead. 315 // This is determined using {@code mLeadDisplayAddress}. 316 private int mLeadDisplayId; 317 Display(@onNull DisplayAddress address, int logicalDisplayId, boolean isEnabled, @NonNull String displayGroupName, String brightnessThrottlingMapId, int position, @Nullable DisplayAddress leadDisplayAddress, @Nullable String refreshRateZoneId, @Nullable String refreshRateThermalThrottlingMapId)318 private Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled, 319 @NonNull String displayGroupName, String brightnessThrottlingMapId, int position, 320 @Nullable DisplayAddress leadDisplayAddress, @Nullable String refreshRateZoneId, 321 @Nullable String refreshRateThermalThrottlingMapId) { 322 mAddress = address; 323 mLogicalDisplayId = logicalDisplayId; 324 mIsEnabled = isEnabled; 325 mDisplayGroupName = displayGroupName; 326 mPosition = position; 327 mThermalBrightnessThrottlingMapId = brightnessThrottlingMapId; 328 mLeadDisplayAddress = leadDisplayAddress; 329 mRefreshRateZoneId = refreshRateZoneId; 330 mThermalRefreshRateThrottlingMapId = refreshRateThermalThrottlingMapId; 331 mLeadDisplayId = NO_LEAD_DISPLAY; 332 } 333 334 @Override toString()335 public String toString() { 336 return "{" 337 + "dispId: " + mLogicalDisplayId 338 + "(" + (mIsEnabled ? "ON" : "OFF") + ")" 339 + ", displayGroupName: " + mDisplayGroupName 340 + ", addr: " + mAddress 341 + ((mPosition == POSITION_UNKNOWN) ? "" : ", position: " + mPosition) 342 + ", mThermalBrightnessThrottlingMapId: " + mThermalBrightnessThrottlingMapId 343 + ", mRefreshRateZoneId: " + mRefreshRateZoneId 344 + ", mLeadDisplayId: " + mLeadDisplayId 345 + ", mLeadDisplayAddress: " + mLeadDisplayAddress 346 + ", mThermalRefreshRateThrottlingMapId: " + mThermalRefreshRateThrottlingMapId 347 + "}"; 348 } 349 350 @Override equals(Object obj)351 public boolean equals(Object obj) { 352 if (!(obj instanceof Display)) { 353 return false; 354 } 355 356 Display otherDisplay = (Display) obj; 357 358 return otherDisplay.mIsEnabled == this.mIsEnabled 359 && otherDisplay.mPosition == this.mPosition 360 && otherDisplay.mLogicalDisplayId == this.mLogicalDisplayId 361 && this.mDisplayGroupName.equals(otherDisplay.mDisplayGroupName) 362 && this.mAddress.equals(otherDisplay.mAddress) 363 && Objects.equals(mThermalBrightnessThrottlingMapId, 364 otherDisplay.mThermalBrightnessThrottlingMapId) 365 && Objects.equals(otherDisplay.mRefreshRateZoneId, this.mRefreshRateZoneId) 366 && this.mLeadDisplayId == otherDisplay.mLeadDisplayId 367 && Objects.equals(mLeadDisplayAddress, otherDisplay.mLeadDisplayAddress) 368 && Objects.equals(mThermalRefreshRateThrottlingMapId, 369 otherDisplay.mThermalRefreshRateThrottlingMapId); 370 } 371 372 @Override hashCode()373 public int hashCode() { 374 int result = 1; 375 result = 31 * result + Boolean.hashCode(mIsEnabled); 376 result = 31 * result + mPosition; 377 result = 31 * result + mLogicalDisplayId; 378 result = 31 * result + mDisplayGroupName.hashCode(); 379 result = 31 * result + mAddress.hashCode(); 380 result = 31 * result + Objects.hashCode(mThermalBrightnessThrottlingMapId); 381 result = 31 * result + Objects.hashCode(mRefreshRateZoneId); 382 result = 31 * result + mLeadDisplayId; 383 result = 31 * result + Objects.hashCode(mLeadDisplayAddress); 384 result = 31 * result + Objects.hashCode(mThermalRefreshRateThrottlingMapId); 385 return result; 386 } 387 getAddress()388 public DisplayAddress getAddress() { 389 return mAddress; 390 } 391 getLogicalDisplayId()392 public int getLogicalDisplayId() { 393 return mLogicalDisplayId; 394 } 395 isEnabled()396 public boolean isEnabled() { 397 return mIsEnabled; 398 } 399 getDisplayGroupName()400 public String getDisplayGroupName() { 401 return mDisplayGroupName; 402 } 403 404 @Nullable getRefreshRateZoneId()405 public String getRefreshRateZoneId() { 406 return mRefreshRateZoneId; 407 } 408 409 /** 410 * Gets the id of the thermal brightness throttling map that should be used. 411 * @return The ID of the thermal brightness throttling map that this display should use, 412 * null if unspecified, will fall back to default. 413 */ getThermalBrightnessThrottlingMapId()414 public String getThermalBrightnessThrottlingMapId() { 415 return mThermalBrightnessThrottlingMapId; 416 } 417 418 /** 419 * @return the position that this display is facing. 420 */ getPosition()421 public int getPosition() { 422 return mPosition; 423 } 424 425 /** 426 * @return logical displayId of the display that this one follows. 427 */ getLeadDisplayId()428 public int getLeadDisplayId() { 429 return mLeadDisplayId; 430 } 431 432 /** 433 * @return Display address of the display that this one follows. 434 */ 435 @Nullable getLeadDisplayAddress()436 public DisplayAddress getLeadDisplayAddress() { 437 return mLeadDisplayAddress; 438 } 439 getRefreshRateThermalThrottlingMapId()440 public String getRefreshRateThermalThrottlingMapId() { 441 return mThermalRefreshRateThrottlingMapId; 442 } 443 setLeadDisplayId(int id)444 private void setLeadDisplayId(int id) { 445 mLeadDisplayId = id; 446 } 447 } 448 } 449