Loading services/core/java/com/android/server/display/DeviceStateToLayoutMap.java +44 −19 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.display; import android.annotation.NonNull; import android.hardware.devicestate.DeviceStateManager; import android.os.Environment; import android.util.IndentingPrintWriter; Loading @@ -23,8 +24,10 @@ import android.util.Slog; import android.util.SparseArray; import android.view.DisplayAddress; import com.android.internal.annotations.VisibleForTesting; import com.android.server.display.config.layout.Layouts; import com.android.server.display.config.layout.XmlParser; import com.android.server.display.layout.DisplayIdProducer; import com.android.server.display.layout.Layout; import org.xmlpull.v1.XmlPullParserException; Loading @@ -48,13 +51,28 @@ class DeviceStateToLayoutMap { public static final int STATE_DEFAULT = DeviceStateManager.INVALID_DEVICE_STATE; // Direction of the display relative to the default display, whilst in this state private static final int POSITION_UNKNOWN = Layout.Display.POSITION_UNKNOWN; private static final int POSITION_FRONT = Layout.Display.POSITION_FRONT; private static final int POSITION_REAR = Layout.Display.POSITION_REAR; private static final String FRONT_STRING = "front"; private static final String REAR_STRING = "rear"; private static final String CONFIG_FILE_PATH = "etc/displayconfig/display_layout_configuration.xml"; private final SparseArray<Layout> mLayoutMap = new SparseArray<>(); private final DisplayIdProducer mIdProducer; DeviceStateToLayoutMap(DisplayIdProducer idProducer) { this(idProducer, Environment.buildPath( Environment.getVendorDirectory(), CONFIG_FILE_PATH)); } DeviceStateToLayoutMap() { loadLayoutsFromConfig(); DeviceStateToLayoutMap(DisplayIdProducer idProducer, File configFile) { mIdProducer = idProducer; loadLayoutsFromConfig(configFile); createLayout(STATE_DEFAULT); } Loading @@ -76,24 +94,11 @@ class DeviceStateToLayoutMap { return layout; } private Layout createLayout(int state) { if (mLayoutMap.contains(state)) { Slog.e(TAG, "Attempted to create a second layout for state " + state); return null; } final Layout layout = new Layout(); mLayoutMap.append(state, layout); return layout; } /** * Reads display-layout-configuration files to get the layouts to use for this device. */ private void loadLayoutsFromConfig() { final File configFile = Environment.buildPath( Environment.getVendorDirectory(), CONFIG_FILE_PATH); @VisibleForTesting void loadLayoutsFromConfig(@NonNull File configFile) { if (!configFile.exists()) { return; } Loading @@ -109,10 +114,19 @@ class DeviceStateToLayoutMap { final int state = l.getState().intValue(); final Layout layout = createLayout(state); for (com.android.server.display.config.layout.Display d: l.getDisplay()) { layout.createDisplayLocked( Layout.Display display = layout.createDisplayLocked( DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()), d.isDefaultDisplay(), d.isEnabled()); d.isEnabled(), mIdProducer); if (FRONT_STRING.equals(d.getPosition())) { display.setPosition(POSITION_FRONT); } else if (REAR_STRING.equals(d.getPosition())) { display.setPosition(POSITION_REAR); } else { display.setPosition(POSITION_UNKNOWN); } } } } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) { Loading @@ -120,4 +134,15 @@ class DeviceStateToLayoutMap { + configFile, e); } } private Layout createLayout(int state) { if (mLayoutMap.contains(state)) { Slog.e(TAG, "Attempted to create a second layout for state " + state); return null; } final Layout layout = new Layout(); mLayoutMap.append(state, layout); return layout; } } services/core/java/com/android/server/display/LogicalDisplayMapper.java +12 −4 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import android.view.DisplayAddress; import android.view.DisplayInfo; import com.android.internal.annotations.VisibleForTesting; import com.android.server.display.layout.DisplayIdProducer; import com.android.server.display.layout.Layout; import java.io.PrintWriter; Loading Loading @@ -82,6 +83,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { private static final int UPDATE_STATE_TRANSITION = 1; private static final int UPDATE_STATE_UPDATED = 2; private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1; /** * Temporary display info, used for comparing display configurations. */ Loading Loading @@ -170,6 +173,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { private final ArrayMap<String, Integer> mVirtualDeviceDisplayMapping = new ArrayMap<>(); private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; private final DisplayIdProducer mIdProducer = (isDefault) -> isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++; private Layout mCurrentLayout = null; private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE; private int mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE; Loading @@ -179,7 +184,9 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo, @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot, @NonNull Handler handler) { this(context, repo, listener, syncRoot, handler, new DeviceStateToLayoutMap()); this(context, repo, listener, syncRoot, handler, new DeviceStateToLayoutMap((isDefault) -> isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++)); } LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo, Loading Loading @@ -588,7 +595,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { // Create a logical display for the new display device LogicalDisplay display = createNewLogicalDisplayLocked( device, Layout.assignDisplayIdLocked(false /*isDefault*/)); device, mIdProducer.getId(/* isDefault= */ false)); applyLayoutLocked(); updateLogicalDisplaysLocked(); Loading Loading @@ -621,7 +628,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { & DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY) != 0 && !nextDeviceInfo.address.equals(deviceInfo.address)) { layout.createDisplayLocked(nextDeviceInfo.address, /* isDefault= */ true, /* isEnabled= */ true); /* isDefault= */ true, /* isEnabled= */ true, mIdProducer); applyLayoutLocked(); return; } Loading Loading @@ -1036,7 +1043,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { return; } final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); layout.createDisplayLocked(info.address, /* isDefault= */ true, /* isEnabled= */ true); layout.createDisplayLocked(info.address, /* isDefault= */ true, /* isEnabled= */ true, mIdProducer); } private int assignLayerStackLocked(int displayId) { Loading services/core/java/com/android/server/display/layout/DisplayIdProducer.java 0 → 100644 +30 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.display.layout; /** * Interface for producing logical display ids. */ public interface DisplayIdProducer { /** * Generates a new display ID * @param isDefault if requested display is the default display. * @return the next unique logical display Id. */ int getId(boolean isDefault); } services/core/java/com/android/server/display/layout/Layout.java +66 −5 Original line number Diff line number Diff line Loading @@ -50,15 +50,33 @@ public class Layout { return mDisplays.toString(); } @Override public boolean equals(Object obj) { if (!(obj instanceof Layout)) { return false; } Layout otherLayout = (Layout) obj; return this.mDisplays.equals(otherLayout.mDisplays); } @Override public int hashCode() { return mDisplays.hashCode(); } /** * Creates a simple 1:1 LogicalDisplay mapping for the specified DisplayDevice. * * @param address Address of the device. * @param isDefault Indicates if the device is meant to be the default display. * @param isEnabled Indicates if this display is usable and can be switched on * @return The new layout. */ public Display createDisplayLocked( @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled) { @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled, DisplayIdProducer idProducer) { if (contains(address)) { Slog.w(TAG, "Attempting to add second definition for display-device: " + address); return null; Loading @@ -74,7 +92,7 @@ public class Layout { // Note that the logical display ID is saved into the layout, so when switching between // different layouts, a logical display can be destroyed and later recreated with the // same logical display ID. final int logicalDisplayId = assignDisplayIdLocked(isDefault); final int logicalDisplayId = idProducer.getId(isDefault); final Display display = new Display(address, logicalDisplayId, isEnabled); mDisplays.add(display); Loading Loading @@ -158,25 +176,64 @@ public class Layout { * Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s. */ public static class Display { public static final int POSITION_UNKNOWN = -1; public static final int POSITION_FRONT = 0; public static final int POSITION_REAR = 1; // Address of the display device to map to this display. private final DisplayAddress mAddress; // Logical Display ID to apply to this display. private final int mLogicalDisplayId; // Indicates that this display is not usable and should remain off. // Indicates if this display is usable and can be switched on private final boolean mIsEnabled; // The direction the display faces // {@link DeviceStateToLayoutMap.POSITION_FRONT} or // {@link DeviceStateToLayoutMap.POSITION_REAR}. // {@link DeviceStateToLayoutMap.POSITION_UNKNOWN} is unspecified. private int mPosition; Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled) { mAddress = address; mLogicalDisplayId = logicalDisplayId; mIsEnabled = isEnabled; mPosition = POSITION_UNKNOWN; } @Override public String toString() { return "{addr: " + mAddress + ", dispId: " + mLogicalDisplayId + "(" + (mIsEnabled ? "ON" : "OFF") + ")}"; return "{" + "dispId: " + mLogicalDisplayId + "(" + (mIsEnabled ? "ON" : "OFF") + ")" + ", addr: " + mAddress + ((mPosition == POSITION_UNKNOWN) ? "" : ", position: " + mPosition) + "}"; } @Override public boolean equals(Object obj) { if (!(obj instanceof Display)) { return false; } Display otherDisplay = (Display) obj; return otherDisplay.mIsEnabled == this.mIsEnabled && otherDisplay.mPosition == this.mPosition && otherDisplay.mLogicalDisplayId == this.mLogicalDisplayId && this.mAddress.equals(otherDisplay.mAddress); } @Override public int hashCode() { int result = 1; result = 31 * result + Boolean.hashCode(mIsEnabled); result = 31 * result + mPosition; result = 31 * result + mLogicalDisplayId; result = 31 * result + mAddress.hashCode(); return result; } public DisplayAddress getAddress() { Loading @@ -190,5 +247,9 @@ public class Layout { public boolean isEnabled() { return mIsEnabled; } public void setPosition(int position) { mPosition = position; } } } services/core/xsd/display-layout-config/display-layout-config.xsd +1 −0 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ <xs:complexType name="display"> <xs:sequence> <xs:element name="address" type="xs:nonNegativeInteger"/> <xs:element name="position" type="xs:string" minOccurs="0" maxOccurs="1" /> </xs:sequence> <xs:attribute name="enabled" type="xs:boolean" use="optional" /> <xs:attribute name="defaultDisplay" type="xs:boolean" use="optional" /> Loading Loading
services/core/java/com/android/server/display/DeviceStateToLayoutMap.java +44 −19 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.display; import android.annotation.NonNull; import android.hardware.devicestate.DeviceStateManager; import android.os.Environment; import android.util.IndentingPrintWriter; Loading @@ -23,8 +24,10 @@ import android.util.Slog; import android.util.SparseArray; import android.view.DisplayAddress; import com.android.internal.annotations.VisibleForTesting; import com.android.server.display.config.layout.Layouts; import com.android.server.display.config.layout.XmlParser; import com.android.server.display.layout.DisplayIdProducer; import com.android.server.display.layout.Layout; import org.xmlpull.v1.XmlPullParserException; Loading @@ -48,13 +51,28 @@ class DeviceStateToLayoutMap { public static final int STATE_DEFAULT = DeviceStateManager.INVALID_DEVICE_STATE; // Direction of the display relative to the default display, whilst in this state private static final int POSITION_UNKNOWN = Layout.Display.POSITION_UNKNOWN; private static final int POSITION_FRONT = Layout.Display.POSITION_FRONT; private static final int POSITION_REAR = Layout.Display.POSITION_REAR; private static final String FRONT_STRING = "front"; private static final String REAR_STRING = "rear"; private static final String CONFIG_FILE_PATH = "etc/displayconfig/display_layout_configuration.xml"; private final SparseArray<Layout> mLayoutMap = new SparseArray<>(); private final DisplayIdProducer mIdProducer; DeviceStateToLayoutMap(DisplayIdProducer idProducer) { this(idProducer, Environment.buildPath( Environment.getVendorDirectory(), CONFIG_FILE_PATH)); } DeviceStateToLayoutMap() { loadLayoutsFromConfig(); DeviceStateToLayoutMap(DisplayIdProducer idProducer, File configFile) { mIdProducer = idProducer; loadLayoutsFromConfig(configFile); createLayout(STATE_DEFAULT); } Loading @@ -76,24 +94,11 @@ class DeviceStateToLayoutMap { return layout; } private Layout createLayout(int state) { if (mLayoutMap.contains(state)) { Slog.e(TAG, "Attempted to create a second layout for state " + state); return null; } final Layout layout = new Layout(); mLayoutMap.append(state, layout); return layout; } /** * Reads display-layout-configuration files to get the layouts to use for this device. */ private void loadLayoutsFromConfig() { final File configFile = Environment.buildPath( Environment.getVendorDirectory(), CONFIG_FILE_PATH); @VisibleForTesting void loadLayoutsFromConfig(@NonNull File configFile) { if (!configFile.exists()) { return; } Loading @@ -109,10 +114,19 @@ class DeviceStateToLayoutMap { final int state = l.getState().intValue(); final Layout layout = createLayout(state); for (com.android.server.display.config.layout.Display d: l.getDisplay()) { layout.createDisplayLocked( Layout.Display display = layout.createDisplayLocked( DisplayAddress.fromPhysicalDisplayId(d.getAddress().longValue()), d.isDefaultDisplay(), d.isEnabled()); d.isEnabled(), mIdProducer); if (FRONT_STRING.equals(d.getPosition())) { display.setPosition(POSITION_FRONT); } else if (REAR_STRING.equals(d.getPosition())) { display.setPosition(POSITION_REAR); } else { display.setPosition(POSITION_UNKNOWN); } } } } catch (IOException | DatatypeConfigurationException | XmlPullParserException e) { Loading @@ -120,4 +134,15 @@ class DeviceStateToLayoutMap { + configFile, e); } } private Layout createLayout(int state) { if (mLayoutMap.contains(state)) { Slog.e(TAG, "Attempted to create a second layout for state " + state); return null; } final Layout layout = new Layout(); mLayoutMap.append(state, layout); return layout; } }
services/core/java/com/android/server/display/LogicalDisplayMapper.java +12 −4 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import android.view.DisplayAddress; import android.view.DisplayInfo; import com.android.internal.annotations.VisibleForTesting; import com.android.server.display.layout.DisplayIdProducer; import com.android.server.display.layout.Layout; import java.io.PrintWriter; Loading Loading @@ -82,6 +83,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { private static final int UPDATE_STATE_TRANSITION = 1; private static final int UPDATE_STATE_UPDATED = 2; private static int sNextNonDefaultDisplayId = DEFAULT_DISPLAY + 1; /** * Temporary display info, used for comparing display configurations. */ Loading Loading @@ -170,6 +173,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { private final ArrayMap<String, Integer> mVirtualDeviceDisplayMapping = new ArrayMap<>(); private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; private final DisplayIdProducer mIdProducer = (isDefault) -> isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++; private Layout mCurrentLayout = null; private int mDeviceState = DeviceStateManager.INVALID_DEVICE_STATE; private int mPendingDeviceState = DeviceStateManager.INVALID_DEVICE_STATE; Loading @@ -179,7 +184,9 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo, @NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot, @NonNull Handler handler) { this(context, repo, listener, syncRoot, handler, new DeviceStateToLayoutMap()); this(context, repo, listener, syncRoot, handler, new DeviceStateToLayoutMap((isDefault) -> isDefault ? DEFAULT_DISPLAY : sNextNonDefaultDisplayId++)); } LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo, Loading Loading @@ -588,7 +595,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { // Create a logical display for the new display device LogicalDisplay display = createNewLogicalDisplayLocked( device, Layout.assignDisplayIdLocked(false /*isDefault*/)); device, mIdProducer.getId(/* isDefault= */ false)); applyLayoutLocked(); updateLogicalDisplaysLocked(); Loading Loading @@ -621,7 +628,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { & DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY) != 0 && !nextDeviceInfo.address.equals(deviceInfo.address)) { layout.createDisplayLocked(nextDeviceInfo.address, /* isDefault= */ true, /* isEnabled= */ true); /* isDefault= */ true, /* isEnabled= */ true, mIdProducer); applyLayoutLocked(); return; } Loading Loading @@ -1036,7 +1043,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { return; } final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); layout.createDisplayLocked(info.address, /* isDefault= */ true, /* isEnabled= */ true); layout.createDisplayLocked(info.address, /* isDefault= */ true, /* isEnabled= */ true, mIdProducer); } private int assignLayerStackLocked(int displayId) { Loading
services/core/java/com/android/server/display/layout/DisplayIdProducer.java 0 → 100644 +30 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.display.layout; /** * Interface for producing logical display ids. */ public interface DisplayIdProducer { /** * Generates a new display ID * @param isDefault if requested display is the default display. * @return the next unique logical display Id. */ int getId(boolean isDefault); }
services/core/java/com/android/server/display/layout/Layout.java +66 −5 Original line number Diff line number Diff line Loading @@ -50,15 +50,33 @@ public class Layout { return mDisplays.toString(); } @Override public boolean equals(Object obj) { if (!(obj instanceof Layout)) { return false; } Layout otherLayout = (Layout) obj; return this.mDisplays.equals(otherLayout.mDisplays); } @Override public int hashCode() { return mDisplays.hashCode(); } /** * Creates a simple 1:1 LogicalDisplay mapping for the specified DisplayDevice. * * @param address Address of the device. * @param isDefault Indicates if the device is meant to be the default display. * @param isEnabled Indicates if this display is usable and can be switched on * @return The new layout. */ public Display createDisplayLocked( @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled) { @NonNull DisplayAddress address, boolean isDefault, boolean isEnabled, DisplayIdProducer idProducer) { if (contains(address)) { Slog.w(TAG, "Attempting to add second definition for display-device: " + address); return null; Loading @@ -74,7 +92,7 @@ public class Layout { // Note that the logical display ID is saved into the layout, so when switching between // different layouts, a logical display can be destroyed and later recreated with the // same logical display ID. final int logicalDisplayId = assignDisplayIdLocked(isDefault); final int logicalDisplayId = idProducer.getId(isDefault); final Display display = new Display(address, logicalDisplayId, isEnabled); mDisplays.add(display); Loading Loading @@ -158,25 +176,64 @@ public class Layout { * Describes how a {@link LogicalDisplay} is built from {@link DisplayDevice}s. */ public static class Display { public static final int POSITION_UNKNOWN = -1; public static final int POSITION_FRONT = 0; public static final int POSITION_REAR = 1; // Address of the display device to map to this display. private final DisplayAddress mAddress; // Logical Display ID to apply to this display. private final int mLogicalDisplayId; // Indicates that this display is not usable and should remain off. // Indicates if this display is usable and can be switched on private final boolean mIsEnabled; // The direction the display faces // {@link DeviceStateToLayoutMap.POSITION_FRONT} or // {@link DeviceStateToLayoutMap.POSITION_REAR}. // {@link DeviceStateToLayoutMap.POSITION_UNKNOWN} is unspecified. private int mPosition; Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled) { mAddress = address; mLogicalDisplayId = logicalDisplayId; mIsEnabled = isEnabled; mPosition = POSITION_UNKNOWN; } @Override public String toString() { return "{addr: " + mAddress + ", dispId: " + mLogicalDisplayId + "(" + (mIsEnabled ? "ON" : "OFF") + ")}"; return "{" + "dispId: " + mLogicalDisplayId + "(" + (mIsEnabled ? "ON" : "OFF") + ")" + ", addr: " + mAddress + ((mPosition == POSITION_UNKNOWN) ? "" : ", position: " + mPosition) + "}"; } @Override public boolean equals(Object obj) { if (!(obj instanceof Display)) { return false; } Display otherDisplay = (Display) obj; return otherDisplay.mIsEnabled == this.mIsEnabled && otherDisplay.mPosition == this.mPosition && otherDisplay.mLogicalDisplayId == this.mLogicalDisplayId && this.mAddress.equals(otherDisplay.mAddress); } @Override public int hashCode() { int result = 1; result = 31 * result + Boolean.hashCode(mIsEnabled); result = 31 * result + mPosition; result = 31 * result + mLogicalDisplayId; result = 31 * result + mAddress.hashCode(); return result; } public DisplayAddress getAddress() { Loading @@ -190,5 +247,9 @@ public class Layout { public boolean isEnabled() { return mIsEnabled; } public void setPosition(int position) { mPosition = position; } } }
services/core/xsd/display-layout-config/display-layout-config.xsd +1 −0 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ <xs:complexType name="display"> <xs:sequence> <xs:element name="address" type="xs:nonNegativeInteger"/> <xs:element name="position" type="xs:string" minOccurs="0" maxOccurs="1" /> </xs:sequence> <xs:attribute name="enabled" type="xs:boolean" use="optional" /> <xs:attribute name="defaultDisplay" type="xs:boolean" use="optional" /> Loading