/* * Copyright (C) 2016 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.systeminterface; import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING; import static android.view.Display.DEFAULT_DISPLAY; import static com.android.car.CarServiceUtils.getContentResolverForUser; import static com.android.car.CarServiceUtils.isEventOfType; import static com.android.car.util.BrightnessUtils.GAMMA_SPACE_MAX; import static com.android.car.util.BrightnessUtils.convertGammaToLinear; import static com.android.car.util.BrightnessUtils.convertLinearToGamma; import android.car.builtin.display.DisplayManagerHelper; import android.car.builtin.os.UserManagerHelper; import android.car.builtin.power.PowerManagerHelper; import android.car.builtin.util.Slogf; import android.car.user.CarUserManager.UserLifecycleListener; import android.car.user.UserLifecycleEventFilter; import android.content.Context; import android.database.ContentObserver; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings.SettingNotFoundException; import android.provider.Settings.System; import android.util.Log; import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.view.Display; import com.android.car.CarLog; import com.android.car.internal.util.IntArray; import com.android.car.power.CarPowerManagementService; import com.android.car.user.CarUserService; import com.android.car.util.BrightnessUtils; import com.android.internal.annotations.GuardedBy; /** * Interface that abstracts display operations */ public interface DisplayInterface { /** * Sets the required services. * * @param carPowerManagementService {@link CarPowerManagementService} to listen to car power * management changes * @param carUserService {@link CarUserService} to listen to service life cycle * changes */ void init(CarPowerManagementService carPowerManagementService, CarUserService carUserService); /** * Sets display brightness. * * @param brightness Level from 0 to 100% */ void setDisplayBrightness(int brightness); /** * Sets display brightness with the given displayId. * * @param displayId ID of a display. * @param brightness Level from 0 to 100. */ void setDisplayBrightness(int displayId, int brightness); /** * Turns on or off display with the given displayId. * * @param displayId ID of a display. * @param on {@code true} to turn on, {@code false} to turn off. */ void setDisplayState(int displayId, boolean on); /** * Turns on or off all displays. * * @param on {@code true} to turn on, {@code false} to turn off. */ void setAllDisplayState(boolean on); /** * Starts monitoring the display state change. *

When there is a change, {@link CarPowerManagementService} is notified. */ void startDisplayStateMonitoring(); /** * Stops monitoring the display state change. */ void stopDisplayStateMonitoring(); /** * Gets the current on/off state of displays. * * @return {@code true}, if any display is turned on. Otherwise, {@code false}. */ boolean isAnyDisplayEnabled(); /** * Gets the current on/off state of display with the given displayId. * * @param displayId ID of a display. */ boolean isDisplayEnabled(int displayId); /** * Refreshing display brightness. Used when user is switching and car turned on. */ void refreshDisplayBrightness(); /** * Refreshing display brightness with the given displayId. * Used when brightness change is observed. * * @param displayId ID of a display. */ void refreshDisplayBrightness(int displayId); /** * Default implementation of display operations */ class DefaultImpl implements DisplayInterface { private static final String TAG = DisplayInterface.class.getSimpleName(); private static final boolean DEBUG = Slogf.isLoggable(TAG, Log.DEBUG); private static final int INVALID_DISPLAY_BRIGHTNESS = -1; private final Context mContext; private final DisplayManager mDisplayManager; private final Object mLock = new Object(); private final int mMaximumBacklight; private final int mMinimumBacklight; private final WakeLockInterface mWakeLockInterface; @GuardedBy("mLock") private CarPowerManagementService mCarPowerManagementService; @GuardedBy("mLock") private CarUserService mCarUserService; @GuardedBy("mLock") private final SparseBooleanArray mDisplayStateSet = new SparseBooleanArray(); @GuardedBy("mLock") private final SparseIntArray mDisplayBrightnessSet = new SparseIntArray(); private final UserManager mUserManager; private final ContentObserver mBrightnessObserver = new ContentObserver(new Handler(Looper.getMainLooper())) { @Override public void onChange(boolean selfChange) { Slogf.i(TAG, "Brightness change from Settings: selfChange=%b", selfChange); refreshDisplayBrightness(); } }; private final DisplayManager.DisplayListener mDisplayListener = new DisplayListener() { @Override public void onDisplayAdded(int displayId) { Slogf.i(TAG, "onDisplayAdded: displayId=%d", displayId); synchronized (mLock) { mDisplayStateSet.put(displayId, isDisplayOn(displayId)); mDisplayBrightnessSet.put(displayId, INVALID_DISPLAY_BRIGHTNESS); } } @Override public void onDisplayRemoved(int displayId) { Slogf.i(TAG, "onDisplayRemoved: displayId=%d", displayId); synchronized (mLock) { mDisplayStateSet.delete(displayId); mDisplayBrightnessSet.delete(displayId); } } @Override public void onDisplayChanged(int displayId) { Slogf.i(TAG, "onDisplayChanged: displayId=%d", displayId); handleDisplayChanged(displayId); } }; DefaultImpl(Context context, WakeLockInterface wakeLockInterface) { mContext = context; mDisplayManager = context.getSystemService(DisplayManager.class); mMaximumBacklight = PowerManagerHelper.getMaximumScreenBrightnessSetting(context); mMinimumBacklight = PowerManagerHelper.getMinimumScreenBrightnessSetting(context); mWakeLockInterface = wakeLockInterface; synchronized (mLock) { for (Display display : mDisplayManager.getDisplays()) { int displayId = display.getDisplayId(); mDisplayStateSet.put(displayId, isDisplayOn(displayId)); mDisplayBrightnessSet.put(displayId, INVALID_DISPLAY_BRIGHTNESS); } } mUserManager = context.getSystemService(UserManager.class); } private final UserLifecycleListener mUserLifecycleListener = event -> { if (!isEventOfType(TAG, event, USER_LIFECYCLE_EVENT_TYPE_SWITCHING)) { return; } if (DEBUG) { Slogf.d(TAG, "DisplayInterface.DefaultImpl.onEvent(%s)", event); } onUsersUpdate(); }; @Override public void refreshDisplayBrightness() { refreshDisplayBrightness(DEFAULT_DISPLAY); } @Override public void refreshDisplayBrightness(int displayId) { CarPowerManagementService carPowerManagementService = null; synchronized (mLock) { carPowerManagementService = mCarPowerManagementService; } if (carPowerManagementService == null) { Slogf.e(CarLog.TAG_POWER, "Could not set brightness: " + "no CarPowerManagementService"); return; } if (UserManagerHelper.isVisibleBackgroundUsersSupported(mUserManager)) { refreshDisplayBrightnessFromDisplay(carPowerManagementService, displayId); } else { refreshDisplayBrigtnessFromSetting(carPowerManagementService); } } private void refreshDisplayBrightnessFromDisplay( CarPowerManagementService carPowerManagementService, int displayId) { int linear = BrightnessUtils.brightnessFloatToInt( DisplayManagerHelper.getBrightness(mContext, displayId)); int gamma = convertLinearToGamma(linear, mMinimumBacklight, mMaximumBacklight); int percentBright = convertGammaToPercentBright(gamma); Slogf.i(TAG, "Refreshing percent brightness(from display %d) to %d", displayId, percentBright); carPowerManagementService.sendDisplayBrightness(displayId, percentBright); } private void refreshDisplayBrigtnessFromSetting( CarPowerManagementService carPowerManagementService) { int gamma = GAMMA_SPACE_MAX; try { int linear = System.getInt(getContentResolverForUser(mContext, UserHandle.CURRENT.getIdentifier()), System.SCREEN_BRIGHTNESS); gamma = convertLinearToGamma(linear, mMinimumBacklight, mMaximumBacklight); } catch (SettingNotFoundException e) { Slogf.e(CarLog.TAG_POWER, "Could not get SCREEN_BRIGHTNESS: ", e); } int percentBright = convertGammaToPercentBright(gamma); Slogf.i(TAG, "Refreshing percent brightness(from Setting) to %d", percentBright); carPowerManagementService.sendDisplayBrightness(percentBright); } private static int convertGammaToPercentBright(int gamma) { return (gamma * 100 + ((GAMMA_SPACE_MAX + 1) / 2)) / GAMMA_SPACE_MAX; } private void handleDisplayChanged(int displayId) { refreshDisplayBrightness(displayId); boolean isOn = isDisplayOn(displayId); CarPowerManagementService service; synchronized (mLock) { boolean state = mDisplayStateSet.get(displayId, false); if (state == isOn) { // same as what is set return; } service = mCarPowerManagementService; } service.handleDisplayChanged(displayId, isOn); } private boolean isDisplayOn(int displayId) { Display disp = mDisplayManager.getDisplay(displayId); if (disp == null) { return false; } return disp.getState() == Display.STATE_ON; } @Override public void setDisplayBrightness(int percentBright) { setDisplayBrightness(DEFAULT_DISPLAY, percentBright); } @Override public void setDisplayBrightness(int displayId, int percentBright) { synchronized (mLock) { if (percentBright == mDisplayBrightnessSet.get(displayId)) { // We have already set the value last time. Skipping return; } mDisplayBrightnessSet.put(displayId, percentBright); } int gamma = (percentBright * GAMMA_SPACE_MAX + 50) / 100; int linear = convertGammaToLinear(gamma, mMinimumBacklight, mMaximumBacklight); if (UserManagerHelper.isVisibleBackgroundUsersSupported(mUserManager)) { DisplayManagerHelper.setBrightness(mContext, displayId, BrightnessUtils.brightnessIntToFloat(linear)); } else { System.putInt( getContentResolverForUser(mContext, UserHandle.CURRENT.getIdentifier()), System.SCREEN_BRIGHTNESS, linear); } } @Override public void init(CarPowerManagementService carPowerManagementService, CarUserService carUserService) { synchronized (mLock) { mCarPowerManagementService = carPowerManagementService; mCarUserService = carUserService; } } @Override public void startDisplayStateMonitoring() { Slogf.i(TAG, "Starting to monitor display state change"); CarPowerManagementService carPowerManagementService; CarUserService carUserService; synchronized (mLock) { carPowerManagementService = mCarPowerManagementService; carUserService = mCarUserService; } UserLifecycleEventFilter userSwitchingEventFilter = new UserLifecycleEventFilter.Builder() .addEventType(USER_LIFECYCLE_EVENT_TYPE_SWITCHING).build(); carUserService.addUserLifecycleListener(userSwitchingEventFilter, mUserLifecycleListener); if (UserManagerHelper.isVisibleBackgroundUsersSupported(mUserManager)) { DisplayManagerHelper.registerDisplayListener(mContext, mDisplayListener, carPowerManagementService.getHandler(), DisplayManagerHelper.EVENT_FLAG_DISPLAY_ADDED | DisplayManagerHelper.EVENT_FLAG_DISPLAY_REMOVED | DisplayManagerHelper.EVENT_FLAG_DISPLAY_CHANGED | DisplayManagerHelper.EVENT_FLAG_DISPLAY_BRIGHTNESS); } else { getContentResolverForUser(mContext, UserHandle.ALL.getIdentifier()) .registerContentObserver(System.getUriFor(System.SCREEN_BRIGHTNESS), false, mBrightnessObserver); } for (Display display : mDisplayManager.getDisplays()) { int displayId = display.getDisplayId(); refreshDisplayBrightness(displayId); } } @Override public void stopDisplayStateMonitoring() { CarUserService carUserService; synchronized (mLock) { carUserService = mCarUserService; } carUserService.removeUserLifecycleListener(mUserLifecycleListener); if (UserManagerHelper.isVisibleBackgroundUsersSupported(mUserManager)) { mDisplayManager.unregisterDisplayListener(mDisplayListener); } else { getContentResolverForUser(mContext, UserHandle.ALL.getIdentifier()) .unregisterContentObserver(mBrightnessObserver); } } @Override public void setDisplayState(int displayId, boolean on) { CarPowerManagementService carPowerManagementService; synchronized (mLock) { carPowerManagementService = mCarPowerManagementService; if (on && carPowerManagementService != null && !carPowerManagementService.canTurnOnDisplay(displayId)) { Slogf.i(CarLog.TAG_POWER, "ignore turning on display %d because " + "CarPowerManagementService doesn't support it", displayId); return; } mDisplayStateSet.put(displayId, on); } if (on) { mWakeLockInterface.switchToFullWakeLock(displayId); Slogf.i(CarLog.TAG_POWER, "on display %d", displayId); } else { mWakeLockInterface.switchToPartialWakeLock(displayId); Slogf.i(CarLog.TAG_POWER, "off display %d", displayId); PowerManagerHelper.goToSleep(mContext, displayId, SystemClock.uptimeMillis()); } if (carPowerManagementService != null) { carPowerManagementService.handleDisplayChanged(displayId, on); } } @Override public void setAllDisplayState(boolean on) { IntArray displayIds = new IntArray(); synchronized (mLock) { for (int i = 0; i < mDisplayStateSet.size(); i++) { displayIds.add(mDisplayStateSet.keyAt(i)); } } // setDisplayState has a binder call to system_server. Should not wrap setDisplayState // with a lock. for (int i = 0; i < displayIds.size(); i++) { int displayId = displayIds.get(i); try { setDisplayState(displayId, on); } catch (IllegalArgumentException e) { Slogf.w(TAG, "Cannot set display(%d) state(%b)", displayId, on); } } } @Override public boolean isAnyDisplayEnabled() { synchronized (mLock) { for (int i = 0; i < mDisplayStateSet.size(); i++) { if (isDisplayEnabled(mDisplayStateSet.keyAt(i))) { return true; } } } return false; } @Override public boolean isDisplayEnabled(int displayId) { return isDisplayOn(displayId); } private void onUsersUpdate() { synchronized (mLock) { if (mCarPowerManagementService == null) { // CarPowerManagementService is not connected yet return; } // We need to reset last value mDisplayBrightnessSet.put(DEFAULT_DISPLAY, INVALID_DISPLAY_BRIGHTNESS); } refreshDisplayBrightness(); } } }