• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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