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 /* powerThrottlingMapId= */ null); 85 } 86 87 /** 88 * Creates a simple 1:1 LogicalDisplay mapping for the specified DisplayDevice. 89 * 90 * @param address Address of the device. 91 * @param isDefault Indicates if the device is meant to be the default display. 92 * @param isEnabled Indicates if this display is usable and can be switched on 93 * @param displayGroupName Name of the display group to which the display is assigned. 94 * @param idProducer Produces the logical display id. 95 * @param position Indicates the position this display is facing in this layout. 96 * @param leadDisplayAddress Address of a display that this one follows ({@code null} if none). 97 * @param brightnessThrottlingMapId Name of which brightness throttling policy should be used. 98 * @param refreshRateZoneId Layout limited refresh rate zone name. 99 * @param refreshRateThermalThrottlingMapId Name of which refresh rate throttling 100 * policy should be used. 101 * @param powerThrottlingMapId Name of which power throttling policy should be used. 102 * 103 * @exception IllegalArgumentException When a default display owns a display group other than 104 * DEFAULT_DISPLAY_GROUP. 105 */ 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, @Nullable String powerThrottlingMapId)106 public void createDisplayLocked( 107 @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled, 108 String displayGroupName, DisplayIdProducer idProducer, int position, 109 @Nullable DisplayAddress leadDisplayAddress, String brightnessThrottlingMapId, 110 @Nullable String refreshRateZoneId, 111 @Nullable String refreshRateThermalThrottlingMapId, 112 @Nullable String powerThrottlingMapId) { 113 if (contains(address)) { 114 Slog.w(TAG, "Attempting to add second definition for display-device: " + address); 115 return; 116 } 117 118 // See if we're dealing with the "default" display 119 if (isDefault && getById(DEFAULT_DISPLAY) != null) { 120 Slog.w(TAG, "Ignoring attempt to add a second default display: " + address); 121 return; 122 } 123 124 if (displayGroupName == null) { 125 displayGroupName = DEFAULT_DISPLAY_GROUP_NAME; 126 } 127 if (isDefault && !displayGroupName.equals(DEFAULT_DISPLAY_GROUP_NAME)) { 128 throw new IllegalArgumentException("Default display should own DEFAULT_DISPLAY_GROUP"); 129 } 130 if (isDefault && leadDisplayAddress != null) { 131 throw new IllegalArgumentException("Default display cannot have a lead display"); 132 } 133 if (address.equals(leadDisplayAddress)) { 134 throw new IllegalArgumentException("Lead display address cannot be the same as display " 135 + " address"); 136 } 137 // Assign a logical display ID and create the new display. 138 // Note that the logical display ID is saved into the layout, so when switching between 139 // different layouts, a logical display can be destroyed and later recreated with the 140 // same logical display ID. 141 final int logicalDisplayId = idProducer.getId(isDefault); 142 143 final Display display = new Display(address, logicalDisplayId, isEnabled, displayGroupName, 144 brightnessThrottlingMapId, position, leadDisplayAddress, refreshRateZoneId, 145 refreshRateThermalThrottlingMapId, powerThrottlingMapId); 146 147 mDisplays.add(display); 148 } 149 150 /** 151 * @param id The ID of the display to remove. 152 */ removeDisplayLocked(int id)153 public void removeDisplayLocked(int id) { 154 Display display = getById(id); 155 if (display != null) { 156 mDisplays.remove(display); 157 } 158 } 159 160 /** 161 * Applies post-processing to displays to make sure the information of each display is 162 * up-to-date. 163 * 164 * <p>At creation of a display, lead display is specified by display address. At post 165 * processing, we convert it to logical display ID. 166 */ postProcessLocked()167 public void postProcessLocked() { 168 for (int i = 0; i < mDisplays.size(); i++) { 169 Display display = mDisplays.get(i); 170 if (display.getLogicalDisplayId() == DEFAULT_DISPLAY) { 171 display.setLeadDisplayId(NO_LEAD_DISPLAY); 172 continue; 173 } 174 DisplayAddress leadDisplayAddress = display.getLeadDisplayAddress(); 175 if (leadDisplayAddress == null) { 176 display.setLeadDisplayId(NO_LEAD_DISPLAY); 177 continue; 178 } 179 Display leadDisplay = getByAddress(leadDisplayAddress); 180 if (leadDisplay == null) { 181 throw new IllegalArgumentException("Cannot find a lead display whose address is " 182 + leadDisplayAddress); 183 } 184 if (!TextUtils.equals(display.getDisplayGroupName(), 185 leadDisplay.getDisplayGroupName())) { 186 throw new IllegalArgumentException("Lead display(" + leadDisplay + ") should be in " 187 + "the same display group of the display(" + display + ")"); 188 } 189 if (hasCyclicLeadDisplay(display)) { 190 throw new IllegalArgumentException("Display(" + display + ") has a cyclic lead " 191 + "display"); 192 } 193 display.setLeadDisplayId(leadDisplay.getLogicalDisplayId()); 194 } 195 } 196 197 /** 198 * @param address The address to check. 199 * 200 * @return True if the specified address is used in this layout. 201 */ contains(@onNull DisplayAddress address)202 public boolean contains(@NonNull DisplayAddress address) { 203 return getByAddress(address) != null; 204 } 205 206 /** 207 * @param id The display ID to check. 208 * 209 * @return The display corresponding to the specified display ID. 210 */ 211 @Nullable getById(int id)212 public Display getById(int id) { 213 for (int i = 0; i < mDisplays.size(); i++) { 214 Display display = mDisplays.get(i); 215 if (id == display.getLogicalDisplayId()) { 216 return display; 217 } 218 } 219 return null; 220 } 221 222 /** 223 * @param address The display address to check. 224 * 225 * @return The display corresponding to the specified address. 226 */ 227 @Nullable getByAddress(@onNull DisplayAddress address)228 public Display getByAddress(@NonNull DisplayAddress address) { 229 for (int i = 0; i < mDisplays.size(); i++) { 230 Display display = mDisplays.get(i); 231 if (address.equals(display.getAddress())) { 232 return display; 233 } 234 if (DisplayAddress.Physical.isPortMatch(address, 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 @Nullable 315 private final String mPowerThrottlingMapId; 316 317 // The ID of the lead display that this display will follow in a layout. -1 means no lead. 318 // This is determined using {@code mLeadDisplayAddress}. 319 private int mLeadDisplayId; 320 Display(@onNull DisplayAddress address, int logicalDisplayId, boolean isEnabled, @NonNull String displayGroupName, String brightnessThrottlingMapId, int position, @Nullable DisplayAddress leadDisplayAddress, @Nullable String refreshRateZoneId, @Nullable String refreshRateThermalThrottlingMapId, @Nullable String powerThrottlingMapId)321 private Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled, 322 @NonNull String displayGroupName, String brightnessThrottlingMapId, int position, 323 @Nullable DisplayAddress leadDisplayAddress, @Nullable String refreshRateZoneId, 324 @Nullable String refreshRateThermalThrottlingMapId, 325 @Nullable String powerThrottlingMapId) { 326 mAddress = address; 327 mLogicalDisplayId = logicalDisplayId; 328 mIsEnabled = isEnabled; 329 mDisplayGroupName = displayGroupName; 330 mPosition = position; 331 mThermalBrightnessThrottlingMapId = brightnessThrottlingMapId; 332 mLeadDisplayAddress = leadDisplayAddress; 333 mRefreshRateZoneId = refreshRateZoneId; 334 mThermalRefreshRateThrottlingMapId = refreshRateThermalThrottlingMapId; 335 mPowerThrottlingMapId = powerThrottlingMapId; 336 mLeadDisplayId = NO_LEAD_DISPLAY; 337 } 338 339 @Override toString()340 public String toString() { 341 return "{" 342 + "dispId: " + mLogicalDisplayId 343 + "(" + (mIsEnabled ? "ON" : "OFF") + ")" 344 + ", displayGroupName: " + mDisplayGroupName 345 + ", addr: " + mAddress 346 + ((mPosition == POSITION_UNKNOWN) ? "" : ", position: " + mPosition) 347 + ", mThermalBrightnessThrottlingMapId: " + mThermalBrightnessThrottlingMapId 348 + ", mRefreshRateZoneId: " + mRefreshRateZoneId 349 + ", mLeadDisplayId: " + mLeadDisplayId 350 + ", mLeadDisplayAddress: " + mLeadDisplayAddress 351 + ", mThermalRefreshRateThrottlingMapId: " + mThermalRefreshRateThrottlingMapId 352 + ", mPowerThrottlingMapId: " + mPowerThrottlingMapId 353 + "}"; 354 } 355 356 @Override equals(Object obj)357 public boolean equals(Object obj) { 358 if (!(obj instanceof Display)) { 359 return false; 360 } 361 362 Display otherDisplay = (Display) obj; 363 364 return otherDisplay.mIsEnabled == this.mIsEnabled 365 && otherDisplay.mPosition == this.mPosition 366 && otherDisplay.mLogicalDisplayId == this.mLogicalDisplayId 367 && this.mDisplayGroupName.equals(otherDisplay.mDisplayGroupName) 368 && this.mAddress.equals(otherDisplay.mAddress) 369 && Objects.equals(mThermalBrightnessThrottlingMapId, 370 otherDisplay.mThermalBrightnessThrottlingMapId) 371 && Objects.equals(otherDisplay.mRefreshRateZoneId, this.mRefreshRateZoneId) 372 && this.mLeadDisplayId == otherDisplay.mLeadDisplayId 373 && Objects.equals(mLeadDisplayAddress, otherDisplay.mLeadDisplayAddress) 374 && Objects.equals(mThermalRefreshRateThrottlingMapId, 375 otherDisplay.mThermalRefreshRateThrottlingMapId) 376 && Objects.equals(mPowerThrottlingMapId, 377 otherDisplay.mPowerThrottlingMapId); 378 } 379 380 @Override hashCode()381 public int hashCode() { 382 int result = 1; 383 result = 31 * result + Boolean.hashCode(mIsEnabled); 384 result = 31 * result + mPosition; 385 result = 31 * result + mLogicalDisplayId; 386 result = 31 * result + mDisplayGroupName.hashCode(); 387 result = 31 * result + mAddress.hashCode(); 388 result = 31 * result + Objects.hashCode(mThermalBrightnessThrottlingMapId); 389 result = 31 * result + Objects.hashCode(mRefreshRateZoneId); 390 result = 31 * result + mLeadDisplayId; 391 result = 31 * result + Objects.hashCode(mLeadDisplayAddress); 392 result = 31 * result + Objects.hashCode(mThermalRefreshRateThrottlingMapId); 393 result = 31 * result + Objects.hashCode(mPowerThrottlingMapId); 394 return result; 395 } 396 getAddress()397 public DisplayAddress getAddress() { 398 return mAddress; 399 } 400 getLogicalDisplayId()401 public int getLogicalDisplayId() { 402 return mLogicalDisplayId; 403 } 404 isEnabled()405 public boolean isEnabled() { 406 return mIsEnabled; 407 } 408 getDisplayGroupName()409 public String getDisplayGroupName() { 410 return mDisplayGroupName; 411 } 412 413 @Nullable getRefreshRateZoneId()414 public String getRefreshRateZoneId() { 415 return mRefreshRateZoneId; 416 } 417 418 /** 419 * Gets the id of the thermal brightness throttling map that should be used. 420 * @return The ID of the thermal brightness throttling map that this display should use, 421 * null if unspecified, will fall back to default. 422 */ getThermalBrightnessThrottlingMapId()423 public String getThermalBrightnessThrottlingMapId() { 424 return mThermalBrightnessThrottlingMapId; 425 } 426 427 /** 428 * @return the position that this display is facing. 429 */ getPosition()430 public int getPosition() { 431 return mPosition; 432 } 433 434 /** 435 * @return logical displayId of the display that this one follows. 436 */ getLeadDisplayId()437 public int getLeadDisplayId() { 438 return mLeadDisplayId; 439 } 440 441 /** 442 * @return Display address of the display that this one follows. 443 */ 444 @Nullable getLeadDisplayAddress()445 public DisplayAddress getLeadDisplayAddress() { 446 return mLeadDisplayAddress; 447 } 448 getRefreshRateThermalThrottlingMapId()449 public String getRefreshRateThermalThrottlingMapId() { 450 return mThermalRefreshRateThrottlingMapId; 451 } 452 453 /** 454 * Gets the id of the power throttling map that should be used. 455 * @return The ID of the power throttling map that this display should use, 456 * null if unspecified, will fall back to default. 457 */ getPowerThrottlingMapId()458 public String getPowerThrottlingMapId() { 459 return mPowerThrottlingMapId; 460 } 461 setLeadDisplayId(int id)462 private void setLeadDisplayId(int id) { 463 mLeadDisplayId = id; 464 } 465 } 466 } 467