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; 18 19 import android.annotation.NonNull; 20 import android.hardware.devicestate.DeviceStateManager; 21 import android.os.Environment; 22 import android.util.IndentingPrintWriter; 23 import android.util.Slog; 24 import android.util.SparseArray; 25 import android.view.DisplayAddress; 26 27 import com.android.internal.annotations.VisibleForTesting; 28 import com.android.server.display.config.layout.Layouts; 29 import com.android.server.display.config.layout.XmlParser; 30 import com.android.server.display.feature.DisplayManagerFlags; 31 import com.android.server.display.layout.DisplayIdProducer; 32 import com.android.server.display.layout.Layout; 33 34 import org.xmlpull.v1.XmlPullParserException; 35 36 import java.io.BufferedInputStream; 37 import java.io.File; 38 import java.io.FileInputStream; 39 import java.io.IOException; 40 import java.io.InputStream; 41 import java.math.BigInteger; 42 43 import javax.xml.datatype.DatatypeConfigurationException; 44 45 /** 46 * Mapping from device states into {@link Layout}s. This allows us to map device 47 * states into specific layouts for the connected displays; particularly useful for 48 * foldable and multi-display devices where the default display and which displays are ON can 49 * change depending on the state of the device. 50 */ 51 class DeviceStateToLayoutMap { 52 private static final String TAG = "DeviceStateToLayoutMap"; 53 54 public static final int STATE_DEFAULT = DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER; 55 56 // Direction of the display relative to the default display, whilst in this state 57 private static final int POSITION_UNKNOWN = Layout.Display.POSITION_UNKNOWN; 58 private static final int POSITION_FRONT = Layout.Display.POSITION_FRONT; 59 private static final int POSITION_REAR = Layout.Display.POSITION_REAR; 60 61 private static final String FRONT_STRING = "front"; 62 private static final String REAR_STRING = "rear"; 63 64 private static final String CONFIG_FILE_PATH = 65 "etc/displayconfig/display_layout_configuration.xml"; 66 67 private static final String DATA_CONFIG_FILE_PATH = 68 "system/displayconfig/display_layout_configuration.xml"; 69 70 private final SparseArray<Layout> mLayoutMap = new SparseArray<>(); 71 private final DisplayIdProducer mIdProducer; 72 private final boolean mIsPortInDisplayLayoutEnabled; 73 DeviceStateToLayoutMap(DisplayIdProducer idProducer, DisplayManagerFlags flags)74 DeviceStateToLayoutMap(DisplayIdProducer idProducer, DisplayManagerFlags flags) { 75 this(idProducer, flags, getConfigFile()); 76 } 77 DeviceStateToLayoutMap(DisplayIdProducer idProducer, DisplayManagerFlags flags, File configFile)78 DeviceStateToLayoutMap(DisplayIdProducer idProducer, DisplayManagerFlags flags, 79 File configFile) { 80 mIsPortInDisplayLayoutEnabled = flags.isPortInDisplayLayoutEnabled(); 81 mIdProducer = idProducer; 82 loadLayoutsFromConfig(configFile); 83 createLayout(STATE_DEFAULT); 84 } 85 getConfigFile()86 static private File getConfigFile() { 87 final File configFileFromDataDir = Environment.buildPath(Environment.getDataDirectory(), 88 DATA_CONFIG_FILE_PATH); 89 if (configFileFromDataDir.exists()) { 90 return configFileFromDataDir; 91 } else { 92 return Environment.buildPath(Environment.getVendorDirectory(), CONFIG_FILE_PATH); 93 } 94 } 95 dumpLocked(IndentingPrintWriter ipw)96 public void dumpLocked(IndentingPrintWriter ipw) { 97 ipw.println("DeviceStateToLayoutMap:"); 98 ipw.println("-----------------------"); 99 ipw.increaseIndent(); 100 101 ipw.println("mIsPortInDisplayLayoutEnabled=" + mIsPortInDisplayLayoutEnabled); 102 ipw.println("Registered Layouts:"); 103 for (int i = 0; i < mLayoutMap.size(); i++) { 104 ipw.println("state(" + mLayoutMap.keyAt(i) + "): " + mLayoutMap.valueAt(i)); 105 } 106 } 107 get(int state)108 Layout get(int state) { 109 Layout layout = mLayoutMap.get(state); 110 if (layout == null) { 111 layout = mLayoutMap.get(STATE_DEFAULT); 112 } 113 return layout; 114 } 115 size()116 int size() { 117 return mLayoutMap.size(); 118 } 119 120 /** 121 * Reads display-layout-configuration files to get the layouts to use for this device. 122 */ 123 @VisibleForTesting loadLayoutsFromConfig(@onNull File configFile)124 void loadLayoutsFromConfig(@NonNull File configFile) { 125 if (!configFile.exists()) { 126 return; 127 } 128 129 Slog.i(TAG, "Loading display layouts from " + configFile); 130 try (InputStream in = new BufferedInputStream(new FileInputStream(configFile))) { 131 final Layouts layouts = XmlParser.read(in); 132 if (layouts == null) { 133 Slog.i(TAG, "Display layout config not found: " + configFile); 134 return; 135 } 136 for (com.android.server.display.config.layout.Layout l : layouts.getLayout()) { 137 final int state = l.getState().intValue(); 138 final Layout layout = createLayout(state); 139 for (com.android.server.display.config.layout.Display d: l.getDisplay()) { 140 assert layout != null; 141 final DisplayAddress address = getDisplayAddressForLayoutDisplay(d); 142 143 int position = getPosition(d.getPosition()); 144 BigInteger leadDisplayPhysicalId = d.getLeadDisplayAddress(); 145 DisplayAddress leadDisplayAddress = leadDisplayPhysicalId == null ? null 146 : DisplayAddress.fromPhysicalDisplayId( 147 leadDisplayPhysicalId.longValue()); 148 layout.createDisplayLocked( 149 address, 150 d.isDefaultDisplay(), 151 d.isEnabled(), 152 d.getDisplayGroup(), 153 mIdProducer, 154 position, 155 leadDisplayAddress, 156 d.getBrightnessThrottlingMapId(), 157 d.getRefreshRateZoneId(), 158 d.getRefreshRateThermalThrottlingMapId(), 159 d.getPowerThrottlingMapId()); 160 } 161 layout.postProcessLocked(); 162 } 163 } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) { 164 Slog.e(TAG, "Encountered an error while reading/parsing display layout config file: " 165 + configFile, e); 166 } 167 } 168 getDisplayAddressForLayoutDisplay( @onNull com.android.server.display.config.layout.Display display)169 private DisplayAddress getDisplayAddressForLayoutDisplay( 170 @NonNull com.android.server.display.config.layout.Display display) { 171 BigInteger xmlAddress = display.getAddress_optional(); 172 if (xmlAddress != null) { 173 return DisplayAddress.fromPhysicalDisplayId(xmlAddress.longValue()); 174 } 175 if (!mIsPortInDisplayLayoutEnabled || display.getPort_optional() == null) { 176 throw new IllegalArgumentException( 177 "Must specify a display identifier in display layout configuration: " + display); 178 } 179 return DisplayAddress.fromPortAndModel((int) display.getPort_optional().longValue(), 180 /* model= */ null); 181 } 182 getPosition(@onNull String position)183 private int getPosition(@NonNull String position) { 184 int positionInt = POSITION_UNKNOWN; 185 if (FRONT_STRING.equals(position)) { 186 positionInt = POSITION_FRONT; 187 } else if (REAR_STRING.equals(position)) { 188 positionInt = POSITION_REAR; 189 } 190 return positionInt; 191 } 192 createLayout(int state)193 private Layout createLayout(int state) { 194 if (mLayoutMap.contains(state)) { 195 Slog.e(TAG, "Attempted to create a second layout for state " + state); 196 return null; 197 } 198 199 final Layout layout = new Layout(); 200 mLayoutMap.append(state, layout); 201 return layout; 202 } 203 } 204