blob: 01899b86e9d349877e763469f67f5f7f4413402f [file] [log] [blame]
/*
* 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.car.power;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.car.CarOccupantZoneManager;
import android.car.CarOccupantZoneManager.OccupantZoneInfo;
import android.car.ICarOccupantZoneCallback;
import android.car.VehicleAreaSeat;
import android.car.settings.CarSettings;
import android.car.test.mocks.AbstractExtendedMockitoTestCase;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.UserHandle;
import android.os.test.TestLooper;
import android.provider.Settings;
import android.util.SparseIntArray;
import android.view.Display;
import com.android.car.CarLocalServices;
import com.android.car.CarOccupantZoneService;
import com.android.car.OccupantZoneHelper;
import com.android.car.R;
import com.android.car.systeminterface.SystemInterface;
import com.android.server.testutils.OffsettableClock;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
public final class ScreenOffHandlerUnitTest extends AbstractExtendedMockitoTestCase {
private static final String TAG = ScreenOffHandlerUnitTest.class.getSimpleName();
private static final int SCREEN_OFF_TIMEOUT = 60000;
@Mock
private Context mContext;
@Mock
private CarOccupantZoneService mCarOccupantZoneService;
@Mock
private SystemInterface mSystemInterface;
@Mock
private ContentResolver mContentResolver;
@Mock
private Resources mResources;
private OffsettableClock mClock;
private TestLooper mTestLooper;
private OccupantZoneHelper mZoneHelper = new OccupantZoneHelper();
private Runnable mRunnableAtBootComplete;
private ScreenOffHandler mScreenOffHandler;
@Before
public void setUp() throws Exception {
mZoneHelper.setUpOccupantZones(mCarOccupantZoneService, /* hasDriver= */ true,
/* hasFrontPassenger= */ false, /* numRearPassengers= */ 2);
CarLocalServices.removeServiceForTest(CarOccupantZoneService.class);
CarLocalServices.addService(CarOccupantZoneService.class, mCarOccupantZoneService);
mClock = new OffsettableClock.Stopped();
mTestLooper = new TestLooper(mClock::now);
setService();
}
@After
public void tearDown() {
CarLocalServices.removeServiceForTest(CarOccupantZoneService.class);
}
@Test
public void testParseSetting_valid_returnNonNull() throws Exception {
bootComplete();
String value1 = "0:0,1:0,2:1";
String value2 = "0:0,1:0,2:1,3:2,4:1,5:0";
String value3 = "99:0,2:0,11:1";
String value4 = "22:0,420:2";
String value5 = "99:0";
SparseIntArray result;
result = mScreenOffHandler.parseModeAssignmentSettingValue(value1);
assertThat(result).isNotNull();
assertThat(result.size()).isEqualTo(3);
result = mScreenOffHandler.parseModeAssignmentSettingValue(value2);
assertThat(result).isNotNull();
assertThat(result.size()).isEqualTo(6);
result = mScreenOffHandler.parseModeAssignmentSettingValue(value3);
assertThat(result).isNotNull();
assertThat(result.size()).isEqualTo(3);
result = mScreenOffHandler.parseModeAssignmentSettingValue(value4);
assertThat(result).isNotNull();
assertThat(result.size()).isEqualTo(2);
result = mScreenOffHandler.parseModeAssignmentSettingValue(value5);
assertThat(result).isNotNull();
assertThat(result.size()).isEqualTo(1);
}
@Test
public void testParseSetting_invalid_returnNull() throws Exception {
bootComplete();
String value1 = "0:0,0:1,2:1"; // duplicate displayId
String value2 = "0:0,1:0,2:1,3:2,4:1,5:11"; // mode out of range
String value3 = "99:0,2:0,a:1"; // alphabet
String value4 = "22;0,420;2"; // invalid separator ;
String value5 = "0"; // single digit
String value6 = ""; // empty
SparseIntArray result;
result = mScreenOffHandler.parseModeAssignmentSettingValue(value1);
assertThat(result).isNull();
result = mScreenOffHandler.parseModeAssignmentSettingValue(value2);
assertThat(result).isNull();
result = mScreenOffHandler.parseModeAssignmentSettingValue(value3);
assertThat(result).isNull();
result = mScreenOffHandler.parseModeAssignmentSettingValue(value4);
assertThat(result).isNull();
result = mScreenOffHandler.parseModeAssignmentSettingValue(value5);
assertThat(result).isNull();
result = mScreenOffHandler.parseModeAssignmentSettingValue(value6);
assertThat(result).isNull();
}
@Test
public void testUpdateUserActivity_noUserAssigned() throws Exception {
bootComplete();
OccupantZoneInfo zoneInfo = mCarOccupantZoneService.getOccupantZone(
CarOccupantZoneManager.OCCUPANT_TYPE_REAR_PASSENGER,
VehicleAreaSeat.SEAT_ROW_2_LEFT);
assertThat(zoneInfo).isNotNull();
int displayId = mCarOccupantZoneService.getDisplayForOccupant(
zoneInfo.zoneId, CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
assertWithMessage("User for occupant zone(%s)", zoneInfo.zoneId)
.that((mCarOccupantZoneService.getUserForOccupant(zoneInfo.zoneId)))
.isEqualTo(CarOccupantZoneManager.INVALID_USER_ID);
mScreenOffHandler.updateUserActivity(displayId, mClock.now());
advanceTime(SCREEN_OFF_TIMEOUT + 1);
verify(mSystemInterface).setDisplayState(displayId, false);
}
@Test
public void testUpdateUserActivity_userAssigned() throws Exception {
bootComplete();
OccupantZoneInfo zoneInfo = mCarOccupantZoneService.getOccupantZone(
CarOccupantZoneManager.OCCUPANT_TYPE_REAR_PASSENGER,
VehicleAreaSeat.SEAT_ROW_2_LEFT);
assertThat(zoneInfo).isNotNull();
int displayId = mCarOccupantZoneService.getDisplayForOccupant(
zoneInfo.zoneId, CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
int userId = 99;
assertWithMessage("User assignment").that(
mCarOccupantZoneService.assignVisibleUserToOccupantZone(
zoneInfo.zoneId, UserHandle.of(userId), /* flags= */ 0)).isEqualTo(
CarOccupantZoneManager.USER_ASSIGNMENT_RESULT_OK);
ICarOccupantZoneCallback callback = getOccupantZoneCallback();
callback.onOccupantZoneConfigChanged(CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER);
mScreenOffHandler.updateUserActivity(displayId, mClock.now());
advanceTime(SCREEN_OFF_TIMEOUT + 1);
verify(mSystemInterface, never()).setDisplayState(displayId, false);
}
@Test
public void testUserActivity_bootIncomplete() throws Exception {
OccupantZoneInfo zoneInfo = mCarOccupantZoneService.getOccupantZone(
CarOccupantZoneManager.OCCUPANT_TYPE_REAR_PASSENGER,
VehicleAreaSeat.SEAT_ROW_2_LEFT);
assertThat(zoneInfo).isNotNull();
assertWithMessage("User for occupant zone(%s)", zoneInfo.zoneId)
.that((mCarOccupantZoneService.getUserForOccupant(zoneInfo.zoneId)))
.isEqualTo(CarOccupantZoneManager.INVALID_USER_ID);
int displayId = mCarOccupantZoneService.getDisplayForOccupant(
zoneInfo.zoneId, CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
mScreenOffHandler.updateUserActivity(displayId, mClock.now());
advanceTime(SCREEN_OFF_TIMEOUT + 1);
verify(mSystemInterface, never()).setDisplayState(displayId, false);
}
@Test
public void testCanTurnOnDisplay() throws Exception {
bootComplete();
OccupantZoneInfo zoneInfo = mCarOccupantZoneService.getOccupantZone(
CarOccupantZoneManager.OCCUPANT_TYPE_REAR_PASSENGER,
VehicleAreaSeat.SEAT_ROW_2_LEFT);
assertThat(zoneInfo).isNotNull();
int displayId = mCarOccupantZoneService.getDisplayForOccupant(
zoneInfo.zoneId, CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
StringBuilder sb = new StringBuilder().append(displayId).append(":").append(/* OFF */ "0");
updateDisplayPowerModeSetting(sb.toString());
assertWithMessage("Display off")
.that(mScreenOffHandler.canTurnOnDisplay(displayId)).isFalse();
sb = new StringBuilder().append(displayId).append(":").append(/* ON */ "1");
updateDisplayPowerModeSetting(sb.toString());
assertWithMessage("Display on")
.that(mScreenOffHandler.canTurnOnDisplay(displayId)).isTrue();
}
@Test
public void testHandleDisplayStateChange() throws Exception {
bootComplete();
OccupantZoneInfo zoneInfo = mCarOccupantZoneService.getOccupantZone(
CarOccupantZoneManager.OCCUPANT_TYPE_REAR_PASSENGER,
VehicleAreaSeat.SEAT_ROW_2_LEFT);
assertThat(zoneInfo).isNotNull();
int displayId = mCarOccupantZoneService.getDisplayForOccupant(
zoneInfo.zoneId, CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
assertThat(displayId).isNotEqualTo(Display.INVALID_DISPLAY);
assertWithMessage("User for occupant zone(%s)", zoneInfo.zoneId)
.that((mCarOccupantZoneService.getUserForOccupant(zoneInfo.zoneId)))
.isEqualTo(CarOccupantZoneManager.INVALID_USER_ID);
mScreenOffHandler.handleDisplayStateChange(displayId, /* on= */ true);
advanceTime(SCREEN_OFF_TIMEOUT + 1);
verify(mSystemInterface).setDisplayState(displayId, false);
}
private void setService() {
when(mResources.getBoolean(R.bool.config_enablePassengerDisplayPowerSaving))
.thenReturn(true);
when(mResources.getInteger(R.integer.config_noUserScreenOffTimeout))
.thenReturn(SCREEN_OFF_TIMEOUT);
when(mContext.createContextAsUser(any(), anyInt())).thenReturn(mContext);
when(mContext.getResources()).thenReturn(mResources);
when(mContext.getContentResolver()).thenReturn(mContentResolver);
when(mSystemInterface.isDisplayEnabled(anyInt())).thenReturn(true);
doAnswer((invocation) -> {
Runnable r = invocation.getArgument(0);
mRunnableAtBootComplete = r;
return null;
}).when(mSystemInterface).scheduleActionForBootCompleted(any(Runnable.class), any());
mScreenOffHandler = new ScreenOffHandler(mContext, mSystemInterface,
mTestLooper.getLooper(), mClock::now);
mScreenOffHandler.init();
}
private void bootComplete() {
if (mRunnableAtBootComplete == null) return;
mRunnableAtBootComplete.run();
}
private ICarOccupantZoneCallback getOccupantZoneCallback() {
ArgumentCaptor<ICarOccupantZoneCallback> captor =
ArgumentCaptor.forClass(ICarOccupantZoneCallback.class);
verify(mCarOccupantZoneService).registerCallback(captor.capture());
return captor.getValue();
}
private ContentObserver getSettingsObserver() {
ArgumentCaptor<ContentObserver> captor =
ArgumentCaptor.forClass(ContentObserver.class);
verify(mContentResolver).registerContentObserver(
eq(Settings.Global.getUriFor(CarSettings.Global.DISPLAY_POWER_MODE)),
anyBoolean(),
captor.capture());
return captor.getValue();
}
private void updateDisplayPowerModeSetting(String value) {
Settings.Global.putString(mContentResolver, CarSettings.Global.DISPLAY_POWER_MODE, value);
ContentObserver osbserver = getSettingsObserver();
osbserver.onChange(/* selfChange= */ false, /* uri= */ null);
}
private void advanceTime(long timeMs) {
mClock.fastForward(timeMs);
mTestLooper.dispatchAll();
}
}