blob: be7875cb6fe96b119d5d1eb1788005ced6ae9090 [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.clockwork.display.burninprotection;
import android.app.AlarmManager;
import android.content.Context;
import android.content.res.Resources;
import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.util.Log;
import android.view.Display;
import com.android.clockwork.common.WearResourceUtil;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wearable.resources.R;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
* This class preventing screen burn-in by image masking on the top of the windows. Triggered by
* {@link DisplayManager.DisplayListener#onDisplayChanged(int)}.
*/
public class BurnInProtector implements DisplayManager.DisplayListener,
AlarmManager.OnAlarmListener {
static final boolean DEBUG = false;
private static final String TAG = "BurnInProtector";
private static BurnInProtector sInstance = null;
private final DisplayManager mDisplayManager;
private final ImageMaskViewController mImageMaskViewController;
private final Supplier<Long> mSystemClockSupplier;
private final Handler mHandler;
private final AlarmManager mAlarmManager;
private final BrightnessObserver mBrightnessObserver;
private int mDisplayState = Display.STATE_UNKNOWN;
private long mLastOffloadStartTimestamp = -1;
@VisibleForTesting
BurnInProtector(
DisplayManager displayManager,
ImageMaskViewController imageMaskViewController,
Supplier<Long> systemClockSupplier,
Handler handler,
AlarmManager alarmManager,
BrightnessObserver brightnessObserver) {
mDisplayManager = displayManager;
mImageMaskViewController = imageMaskViewController;
mSystemClockSupplier = systemClockSupplier;
mHandler = handler;
mAlarmManager = alarmManager;
mBrightnessObserver = brightnessObserver;
}
public static BurnInProtector create(final Context context) {
sInstance =
new BurnInProtector(
context.getSystemService(DisplayManager.class),
new ImageMaskViewController(context),
SystemClock::elapsedRealtime,
new Handler(Looper.getMainLooper()),
context.getSystemService(AlarmManager.class),
new BrightnessObserver(context));
return sInstance;
}
/**
* Get the current instance of BurnInProtector
*
* @return BurnInProtector singleton instance
*/
public static BurnInProtector instance() {
return sInstance;
}
/**
* Initialize the BurnInProtector.
*
* @param context Context
*/
public void init(Context context) {
final Resources wearableResources = WearResourceUtil.getWearableResources(context);
if (wearableResources == null) {
return;
}
int burnInMaskThreshold = wearableResources.getInteger(
R.integer.config_burnInProtectionMaskThreshold);
// Not initialize when threshold config is 0
if (burnInMaskThreshold == 0) {
return;
}
mDisplayManager.registerDisplayListener(this, mHandler);
}
@Override
public void onDisplayAdded(int displayId) {
//Not used
}
@Override
public void onDisplayRemoved(int displayId) {
//Not used
}
@Override
public void onDisplayChanged(int displayId) {
if (displayId != Display.DEFAULT_DISPLAY) {
return;
}
int displayState = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY).getState();
if (mDisplayState == displayState) {
return;
}
if (DEBUG) {
Log.d(TAG, "onDisplayChanged: displayState=" + displayState);
}
mDisplayState = displayState;
switch (mDisplayState) {
case Display.STATE_DOZE:
if (mLastOffloadStartTimestamp < 0) {
// Register alarm for when the device keeps doze mode. This alarm will be
// canceled if the device enters DOZE_SUSPEND.
mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
mSystemClockSupplier.get() + TimeUnit.MINUTES.toMillis(2),
"burn-in-protector_uadpte",
this, mHandler);
} else {
// Start BrightnessObserver to listen if the device entered STATE_DOZE.
// BrightnessObserver will call tearDown() by itself after sampling once.
mBrightnessObserver.observe();
}
break;
case Display.STATE_DOZE_SUSPEND:
if (mLastOffloadStartTimestamp < 0) {
// Set mLastOffloadStartTimestamp only once for the duration when the image
// mask is enabled.
mLastOffloadStartTimestamp = mSystemClockSupplier.get();
}
// Cancel alarm that was registered in STATE_DOZE
mAlarmManager.cancel(this);
// Stop observing brightness if entering STATE_DOZE_SUSPEND.
mBrightnessObserver.tearDown();
break;
default:
// Always disable burn in protection mask in these states.
disableBurnInProtection();
break;
}
}
/**
* This is called 2 minutes after the device enters STATE_DOZE.
*/
@Override
public void onAlarm() {
if (mDisplayState != Display.STATE_DOZE && mDisplayState != Display.STATE_DOZE_SUSPEND) {
// Stop the alarm when we're no longer in DOZE/DOZE_SUSPEND
return;
}
enableBurnInProtection();
// Register an alarm to update image mask by every single minute.
mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
mSystemClockSupplier.get() + TimeUnit.MINUTES.toMillis(1),
"burn-in-protector_image-update",
this, mHandler);
}
@VisibleForTesting
void disableBurnInProtection() {
mAlarmManager.cancel(this);
mImageMaskViewController.disableImageMask();
mLastOffloadStartTimestamp = -1;
mBrightnessObserver.tearDown();
}
void enableBurnInProtection() {
if (mImageMaskViewController.isEnabled()) {
mImageMaskViewController.updateImageMask();
} else {
mImageMaskViewController.enableImageMask();
}
}
/**
* Trigger single shot update.
*
* @return true if burn-in protection enabled.
*/
public boolean singleShotUpdate() {
if (mLastOffloadStartTimestamp > -1
&& mSystemClockSupplier.get() - mLastOffloadStartTimestamp
> TimeUnit.MINUTES.toMillis(1)
&& mBrightnessObserver.shouldEnableBurnInProtector()) {
Log.d(TAG, "singleShotUpdate: burn-in protector is enabled by display offload.");
enableBurnInProtection();
return true;
}
return false;
}
}