blob: 489814deb119a61d75368090fbb5b3069fc48f8f [file] [log] [blame]
/*
* Copyright (C) 2020 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.car.hardware.power;
import static android.car.hardware.power.PowerComponent.AUDIO;
import static android.car.hardware.power.PowerComponent.BLUETOOTH;
import static android.car.hardware.power.PowerComponent.CELLULAR;
import static android.car.hardware.power.PowerComponent.CPU;
import static android.car.hardware.power.PowerComponent.DISPLAY;
import static android.car.hardware.power.PowerComponent.ETHERNET;
import static android.car.hardware.power.PowerComponent.INPUT;
import static android.car.hardware.power.PowerComponent.LOCATION;
import static android.car.hardware.power.PowerComponent.MEDIA;
import static android.car.hardware.power.PowerComponent.MICROPHONE;
import static android.car.hardware.power.PowerComponent.NFC;
import static android.car.hardware.power.PowerComponent.PROJECTION;
import static android.car.hardware.power.PowerComponent.TRUSTED_DEVICE_DETECTION;
import static android.car.hardware.power.PowerComponent.VISUAL_INTERACTION;
import static android.car.hardware.power.PowerComponent.VOICE_INTERACTION;
import static android.car.hardware.power.PowerComponent.WIFI;
import static com.android.car.test.power.CarPowerPolicyUtil.assertPolicyIdentical;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
import android.annotation.NonNull;
import android.car.Car;
import android.car.hardware.power.CarPowerManager;
import android.car.hardware.power.CarPowerPolicy;
import android.car.hardware.power.CarPowerPolicyFilter;
import android.car.hardware.power.PowerComponent;
import android.car.test.mocks.AbstractExtendedMockitoTestCase;
import android.car.test.mocks.JavaMockitoHelper;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.frameworks.automotive.powerpolicy.internal.ICarPowerPolicySystemNotification;
import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateReq;
import android.hardware.automotive.vehicle.V2_0.VehicleApPowerStateShutdownParam;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.AtomicFile;
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.car.R;
import com.android.car.hal.MockedPowerHalService;
import com.android.car.hal.PowerHalService;
import com.android.car.hal.PowerHalService.PowerState;
import com.android.car.power.CarPowerManagementService;
import com.android.car.power.PowerComponentHandler;
import com.android.car.systeminterface.DisplayInterface;
import com.android.car.systeminterface.SystemInterface;
import com.android.car.systeminterface.SystemStateInterface;
import com.android.car.test.utils.TemporaryFile;
import com.android.car.user.CarUserService;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IVoiceInteractionManagerService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Spy;
import java.time.Duration;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@SmallTest
public class CarPowerManagerUnitTest extends AbstractExtendedMockitoTestCase {
private static final String TAG = CarPowerManagerUnitTest.class.getSimpleName();
private static final long WAIT_TIMEOUT_MS = 5_000;
private static final long WAIT_TIMEOUT_LONG_MS = 10_000;
// A shorter value for use when the test is expected to time out
private static final long WAIT_WHEN_TIMEOUT_EXPECTED_MS = 100;
private final MockDisplayInterface mDisplayInterface = new MockDisplayInterface();
private final MockSystemStateInterface mSystemStateInterface = new MockSystemStateInterface();
@Spy
private final Context mContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
private final Executor mExecutor = mContext.getMainExecutor();
private final TemporaryFile mComponentStateFile;
private MockedPowerHalService mPowerHal;
private SystemInterface mSystemInterface;
private CarPowerManagementService mService;
private CarPowerManager mCarPowerManager;
private PowerComponentHandler mPowerComponentHandler;
@Mock
private Resources mResources;
@Mock
private Car mCar;
@Mock
private CarUserService mCarUserService;
@Mock
private IVoiceInteractionManagerService mVoiceInteractionManagerService;
@Mock
private ICarPowerPolicySystemNotification mPowerPolicyDaemon;
public CarPowerManagerUnitTest() throws Exception {
mComponentStateFile = new TemporaryFile("COMPONENT_STATE_FILE");
}
@Before
public void setUp() throws Exception {
mPowerHal = new MockedPowerHalService(true /*isPowerStateSupported*/,
true /*isDeepSleepAllowed*/, true /*isTimedWakeupAllowed*/);
mSystemInterface = SystemInterface.Builder.defaultSystemInterface(mContext)
.withDisplayInterface(mDisplayInterface)
.withSystemStateInterface(mSystemStateInterface)
.build();
setService();
mCarPowerManager = new CarPowerManager(mCar, mService);
}
@After
public void tearDown() throws Exception {
if (mService != null) {
mService.release();
}
}
@Test
public void testRequestShutdownOnNextSuspend_positive() throws Exception {
setPowerOn();
// Tell it to shutdown
mCarPowerManager.requestShutdownOnNextSuspend();
// Request suspend
setPowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
VehicleApPowerStateShutdownParam.CAN_SLEEP);
// Verify shutdown
assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_SHUTDOWN_START, 0);
}
@Test
public void testRequestShutdownOnNextSuspend_negative() throws Exception {
setPowerOn();
// Do not tell it to shutdown
// Request suspend
setPowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
VehicleApPowerStateShutdownParam.CAN_SLEEP);
// Verify suspend
assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY, 0);
}
@Test
public void testScheduleNextWakeupTime() throws Exception {
setPowerOn();
int wakeTime = 1234;
mCarPowerManager.scheduleNextWakeupTime(wakeTime);
setPowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
VehicleApPowerStateShutdownParam.CAN_SLEEP);
// Verify that we suspended with the requested wake-up time
assertStateReceivedForShutdownOrSleepWithPostpone(
PowerHalService.SET_DEEP_SLEEP_ENTRY, wakeTime);
}
@Test
public void testSetListener() throws Exception {
setPowerOn();
WaitablePowerStateListener listener = new WaitablePowerStateListener(2);
setPowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
VehicleApPowerStateShutdownParam.CAN_SLEEP);
assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY, 0);
int finalState = listener.await();
assertThat(finalState).isEqualTo(PowerHalService.SET_DEEP_SLEEP_ENTRY);
}
@Test
public void testSetListenerWithCompletion() throws Exception {
setPowerOn();
WaitablePowerStateListenerWithCompletion listener =
new WaitablePowerStateListenerWithCompletion(2);
setPowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
VehicleApPowerStateShutdownParam.CAN_SLEEP);
assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY, 0);
int finalState = listener.await();
assertThat(finalState).isEqualTo(PowerHalService.SET_DEEP_SLEEP_ENTRY);
}
@Test
public void testClearListener() throws Exception {
setPowerOn();
// Set a listener with a short timeout, because we expect the timeout to happen
WaitablePowerStateListener listener =
new WaitablePowerStateListener(1, WAIT_WHEN_TIMEOUT_EXPECTED_MS);
mCarPowerManager.clearListener();
setPowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
VehicleApPowerStateShutdownParam.CAN_SLEEP);
assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY, 0);
// Verify that the listener didn't run
assertThrows(IllegalStateException.class, () -> listener.await());
}
@Test
public void testGetPowerState() throws Exception {
setPowerOn();
assertThat(mCarPowerManager.getPowerState()).isEqualTo(PowerHalService.SET_ON);
// Request suspend
setPowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
VehicleApPowerStateShutdownParam.CAN_SLEEP);
assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY, 0);
assertThat(mCarPowerManager.getPowerState())
.isEqualTo(PowerHalService.SET_DEEP_SLEEP_ENTRY);
}
@Test
public void testGetCurrentPowerPolicy() throws Exception {
grantPowerPolicyPermission();
CarPowerPolicy expected = new CarPowerPolicy("test_policy4",
new int[]{AUDIO, MEDIA, DISPLAY, INPUT, CPU},
new int[]{BLUETOOTH, CELLULAR, ETHERNET, LOCATION, MICROPHONE, NFC, PROJECTION,
TRUSTED_DEVICE_DETECTION, VISUAL_INTERACTION, VOICE_INTERACTION, WIFI});
PolicyDefinition[] policyDefinitions = new PolicyDefinition[]{
new PolicyDefinition("test_policy1", new String[]{"WIFI"}, new String[]{"AUDIO"}),
new PolicyDefinition("test_policy2", new String[]{"WIFI", "DISPLAY"},
new String[]{"NFC"}),
new PolicyDefinition("test_policy3", new String[]{"CPU", "INPUT"},
new String[]{"WIFI"}),
new PolicyDefinition("test_policy4", new String[]{"MEDIA", "AUDIO"},
new String[]{})};
for (PolicyDefinition definition : policyDefinitions) {
mService.definePowerPolicy(definition.policyId, definition.enabledComponents,
definition.disabledComponents);
}
for (PolicyDefinition definition : policyDefinitions) {
mCarPowerManager.applyPowerPolicy(definition.policyId);
}
assertPolicyIdentical(expected, mCarPowerManager.getCurrentPowerPolicy());
}
@Test
public void testApplyPowerPolicy() throws Exception {
grantPowerPolicyPermission();
String policyId = "no_change_policy";
mService.definePowerPolicy(policyId, new String[0], new String[0]);
mCarPowerManager.applyPowerPolicy(policyId);
assertThat(mCarPowerManager.getCurrentPowerPolicy().getPolicyId()).isEqualTo(policyId);
}
@Test
public void testApplyPowerPolicy_invalidId() throws Exception {
grantPowerPolicyPermission();
String policyId = "invalid_power_policy";
assertThrows(IllegalArgumentException.class,
() -> mCarPowerManager.applyPowerPolicy(policyId));
}
@Test
public void testApplyPowerPolicy_nullPolicyId() throws Exception {
grantPowerPolicyPermission();
assertThrows(IllegalArgumentException.class, () -> mCarPowerManager.applyPowerPolicy(null));
}
@Test
public void testAddPowerPolicyListener() throws Exception {
grantPowerPolicyPermission();
String policyId = "audio_on_wifi_off";
mService.definePowerPolicy(policyId, new String[]{"AUDIO"}, new String[]{"WIFI"});
MockedPowerPolicyListener listenerAudio = new MockedPowerPolicyListener();
MockedPowerPolicyListener listenerWifi = new MockedPowerPolicyListener();
MockedPowerPolicyListener listenerLocation = new MockedPowerPolicyListener();
CarPowerPolicyFilter filterAudio = new CarPowerPolicyFilter.Builder()
.setComponents(new int[]{PowerComponent.AUDIO}).build();
CarPowerPolicyFilter filterWifi = new CarPowerPolicyFilter.Builder()
.setComponents(new int[]{PowerComponent.WIFI}).build();
CarPowerPolicyFilter filterLocation = new CarPowerPolicyFilter.Builder()
.setComponents(new int[]{PowerComponent.LOCATION}).build();
mCarPowerManager.addPowerPolicyListener(mExecutor, filterAudio, listenerAudio);
mCarPowerManager.addPowerPolicyListener(mExecutor, filterWifi, listenerWifi);
mCarPowerManager.addPowerPolicyListener(mExecutor, filterLocation, listenerLocation);
mCarPowerManager.applyPowerPolicy(policyId);
assertThat(listenerAudio.getCurrentPolicyId()).isEqualTo(policyId);
assertThat(listenerWifi.getCurrentPolicyId()).isEqualTo(policyId);
assertThat(listenerLocation.getCurrentPolicyId()).isNull();
}
@Test
public void testAddPowerPolicyListener_Twice_WithDifferentFilters() throws Exception {
grantPowerPolicyPermission();
String policyId = "audio_on_wifi_off";
mService.definePowerPolicy(policyId, new String[]{"AUDIO"}, new String[]{"WIFI"});
MockedPowerPolicyListener listener = new MockedPowerPolicyListener();
CarPowerPolicyFilter filterAudio = new CarPowerPolicyFilter.Builder()
.setComponents(new int[]{PowerComponent.AUDIO}).build();
CarPowerPolicyFilter filterLocation = new CarPowerPolicyFilter.Builder()
.setComponents(new int[]{PowerComponent.LOCATION}).build();
mCarPowerManager.addPowerPolicyListener(mExecutor, filterAudio, listener);
mCarPowerManager.addPowerPolicyListener(mExecutor, filterLocation, listener);
mCarPowerManager.applyPowerPolicy(policyId);
assertThat(listener.getCurrentPolicyId()).isNull();
}
@Test
public void testAddPowerPolicyListener_nullListener() throws Exception {
MockedPowerPolicyListener listener = new MockedPowerPolicyListener();
CarPowerPolicyFilter filter = new CarPowerPolicyFilter.Builder()
.setComponents(new int[]{PowerComponent.AUDIO}).build();
assertThrows(NullPointerException.class,
() -> mCarPowerManager.addPowerPolicyListener(null, filter, listener));
assertThrows(NullPointerException.class,
() -> mCarPowerManager.addPowerPolicyListener(mExecutor, filter, null));
assertThrows(NullPointerException.class,
() -> mCarPowerManager.addPowerPolicyListener(mExecutor, null, listener));
}
@Test
public void testRemovePowerPolicyListener() throws Exception {
grantPowerPolicyPermission();
String policyId = "audio_on_wifi_off";
mService.definePowerPolicy(policyId, new String[]{"AUDIO"}, new String[]{"WIFI"});
MockedPowerPolicyListener listenerOne = new MockedPowerPolicyListener();
MockedPowerPolicyListener listenerTwo = new MockedPowerPolicyListener();
CarPowerPolicyFilter filterAudio = new CarPowerPolicyFilter.Builder()
.setComponents(new int[]{PowerComponent.AUDIO}).build();
mCarPowerManager.addPowerPolicyListener(mExecutor, filterAudio, listenerOne);
mCarPowerManager.addPowerPolicyListener(mExecutor, filterAudio, listenerTwo);
mCarPowerManager.removePowerPolicyListener(listenerOne);
mCarPowerManager.applyPowerPolicy(policyId);
assertThat(listenerOne.getCurrentPolicyId()).isNull();
assertThat(listenerTwo.getCurrentPolicyId()).isEqualTo(policyId);
}
@Test
public void testRemovePowerPolicyListener_Twice() throws Exception {
grantPowerPolicyPermission();
MockedPowerPolicyListener listener = new MockedPowerPolicyListener();
CarPowerPolicyFilter filter = new CarPowerPolicyFilter.Builder()
.setComponents(new int[]{PowerComponent.AUDIO}).build();
// Remove unregistered listener should not throw an exception.
mCarPowerManager.removePowerPolicyListener(listener);
mCarPowerManager.addPowerPolicyListener(mExecutor, filter, listener);
mCarPowerManager.removePowerPolicyListener(listener);
// Remove the same listener twice should nont throw an exception.
mCarPowerManager.removePowerPolicyListener(listener);
}
@Test
public void testRemovePowerPolicyListener_nullListener() throws Exception {
assertThrows(NullPointerException.class,
() -> mCarPowerManager.removePowerPolicyListener(null));
}
/**
* Helper method to create mService and initialize a test case
*/
private void setService() throws Exception {
Log.i(TAG, "setService(): overridden overlay properties: "
+ ", maxGarageModeRunningDurationInSecs="
+ mResources.getInteger(R.integer.maxGarageModeRunningDurationInSecs));
mPowerComponentHandler = new PowerComponentHandler(mContext, mSystemInterface,
mVoiceInteractionManagerService, new AtomicFile(mComponentStateFile.getFile()));
mService = new CarPowerManagementService(mContext, mResources, mPowerHal, mSystemInterface,
null, mCarUserService, mPowerPolicyDaemon, mPowerComponentHandler,
/* silentModeHwStatePath= */ null, /* silentModeKernelStatePath= */ null,
/* bootReason= */ null);
mService.init();
mService.setShutdownTimersForTest(0, 0);
assertStateReceived(MockedPowerHalService.SET_WAIT_FOR_VHAL, 0);
}
private void assertStateReceived(int expectedState, int expectedParam) throws Exception {
int[] state = mPowerHal.waitForSend(WAIT_TIMEOUT_MS);
assertThat(state).asList().containsExactly(expectedState, expectedParam).inOrder();
}
/**
* Helper method to get the system into ON
*/
private void setPowerOn() throws Exception {
setPowerState(VehicleApPowerStateReq.ON, 0);
int[] state = mPowerHal.waitForSend(WAIT_TIMEOUT_MS);
assertThat(state[0]).isEqualTo(PowerHalService.SET_ON);
}
/**
* Helper to set the PowerHal state
*
* @param stateEnum Requested state enum
* @param stateParam Addition state parameter
*/
private void setPowerState(int stateEnum, int stateParam) {
mPowerHal.setCurrentPowerState(new PowerState(stateEnum, stateParam));
}
private void assertStateReceivedForShutdownOrSleepWithPostpone(
int lastState, int stateParameter) throws Exception {
long startTime = System.currentTimeMillis();
while (true) {
int[] state = mPowerHal.waitForSend(WAIT_TIMEOUT_LONG_MS);
if (state[0] == lastState) {
assertThat(state[1]).isEqualTo(stateParameter);
return;
}
assertThat(state[0]).isEqualTo(PowerHalService.SET_SHUTDOWN_POSTPONE);
assertThat(System.currentTimeMillis() - startTime).isLessThan(WAIT_TIMEOUT_LONG_MS);
}
}
private void grantPowerPolicyPermission() {
when(mCar.getContext()).thenReturn(mContext);
doReturn(PackageManager.PERMISSION_GRANTED).when(mContext)
.checkCallingOrSelfPermission(Car.PERMISSION_CONTROL_CAR_POWER_POLICY);
doReturn(PackageManager.PERMISSION_GRANTED).when(mContext)
.checkCallingOrSelfPermission(Car.PERMISSION_READ_CAR_POWER_POLICY);
}
private static final class MockDisplayInterface implements DisplayInterface {
private final Object mLock = new Object();
@GuardedBy("mLock")
private boolean mDisplayOn = true;
private final Semaphore mDisplayStateWait = new Semaphore(0);
@Override
public void setDisplayBrightness(int brightness) {}
@Override
public void setDisplayState(boolean on) {
synchronized (mLock) {
mDisplayOn = on;
}
mDisplayStateWait.release();
}
public boolean waitForDisplayStateChange(long timeoutMs) throws Exception {
JavaMockitoHelper.await(mDisplayStateWait, timeoutMs);
synchronized (mLock) {
return mDisplayOn;
}
}
@Override
public void startDisplayStateMonitoring(CarPowerManagementService service) {}
@Override
public void stopDisplayStateMonitoring() {}
@Override
public void refreshDisplayBrightness() {}
@Override
public boolean isDisplayEnabled() {
synchronized (mLock) {
return mDisplayOn;
}
}
}
/**
* Helper class to set a power-state listener,
* verify that the listener gets called the
* right number of times, and return the final
* power state.
*/
private final class WaitablePowerStateListener {
private final CountDownLatch mLatch;
private int mListenedState = -1;
private long mTimeoutValue = WAIT_TIMEOUT_MS;
WaitablePowerStateListener(int initialCount, long customTimeout) {
this(initialCount);
mTimeoutValue = customTimeout;
}
WaitablePowerStateListener(int initialCount) {
mLatch = new CountDownLatch(initialCount);
mCarPowerManager.setListener(
(state) -> {
mListenedState = state;
mLatch.countDown();
});
}
int await() throws Exception {
JavaMockitoHelper.await(mLatch, WAIT_TIMEOUT_MS);
return mListenedState;
}
}
/**
* Helper class to set a power-state listener with completion,
* verify that the listener gets called the right number of times,
* verify that the CompletableFuture is provided, complete the
* CompletableFuture, and return the final power state.
*/
private final class WaitablePowerStateListenerWithCompletion {
private final CountDownLatch mLatch;
private int mListenedState = -1;
WaitablePowerStateListenerWithCompletion(int initialCount) {
mLatch = new CountDownLatch(initialCount);
mCarPowerManager.setListenerWithCompletion(
(state, future) -> {
mListenedState = state;
if (state == PowerHalService.SET_SHUTDOWN_PREPARE) {
assertThat(future).isNotNull();
future.complete(null);
} else {
assertThat(future).isNull();
}
mLatch.countDown();
});
}
int await() throws Exception {
JavaMockitoHelper.await(mLatch, WAIT_TIMEOUT_MS);
return mListenedState;
}
}
private static final class MockSystemStateInterface implements SystemStateInterface {
private final Semaphore mShutdownWait = new Semaphore(0);
private final Semaphore mSleepWait = new Semaphore(0);
private final Semaphore mSleepExitWait = new Semaphore(0);
private boolean mWakeupCausedByTimer = false;
@Override
public void shutdown() {
mShutdownWait.release();
}
public void waitForShutdown(long timeoutMs) throws Exception {
JavaMockitoHelper.await(mShutdownWait, timeoutMs);
}
@Override
public boolean enterDeepSleep() {
mSleepWait.release();
try {
mSleepExitWait.acquire();
} catch (InterruptedException e) {
}
return true;
}
public void waitForSleepEntryAndWakeup(long timeoutMs) throws Exception {
JavaMockitoHelper.await(mSleepWait, timeoutMs);
mSleepExitWait.release();
}
@Override
public void scheduleActionForBootCompleted(Runnable action, Duration delay) {}
@Override
public boolean isWakeupCausedByTimer() {
Log.i(TAG, "isWakeupCausedByTimer:" + mWakeupCausedByTimer);
return mWakeupCausedByTimer;
}
public synchronized void setWakeupCausedByTimer(boolean set) {
mWakeupCausedByTimer = set;
}
@Override
public boolean isSystemSupportingDeepSleep() {
return true;
}
}
private final class MockedPowerPolicyListener implements
CarPowerManager.CarPowerPolicyListener {
private static final int MAX_LISTENER_WAIT_TIME_SEC = 1;
private final CountDownLatch mLatch = new CountDownLatch(1);
private String mCurrentPolicyId;
@Override
public void onPolicyChanged(@NonNull CarPowerPolicy policy) {
mCurrentPolicyId = policy.getPolicyId();
mLatch.countDown();
}
public String getCurrentPolicyId() throws Exception {
if (mLatch.await(MAX_LISTENER_WAIT_TIME_SEC, TimeUnit.SECONDS)) {
return mCurrentPolicyId;
}
return null;
}
}
private static final class PolicyDefinition {
public final String policyId;
public final String[] enabledComponents;
public final String[] disabledComponents;
private PolicyDefinition(String policyId, String[] enabledComponents,
String[] disabledComponents) {
this.policyId = policyId;
this.enabledComponents = enabledComponents;
this.disabledComponents = disabledComponents;
}
}
}