blob: 73be5113c128ddfc3f0cf1d5830c20f26af5851f [file] [log] [blame]
/*
* Copyright (C) 2017 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;
import static com.android.wearable.resources.R.bool.config_disableAODWhilePlugged;
import android.Manifest;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.app.WallpaperInfo;
import android.app.WallpaperManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.hardware.display.DisplayManagerInternal;
import android.os.BatteryManager;
import android.os.Binder;
import android.os.Build;
import android.os.PowerManager;
import android.os.SystemClock;
import android.provider.Settings;
import android.util.Log;
import com.android.clockwork.common.WearResourceUtil;
import com.android.clockwork.display.burninprotection.BurnInProtector;
import com.android.clockwork.power.AmbientConfig;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.SystemService;
import com.google.android.clockwork.display.IWearDisplayService;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Arrays;
/** Wear-specific display mechanisms. */
public class WearDisplayService extends SystemService {
private static final String TAG = "WearDisplayService";
private static final String SET_DISPLAY_OFFSET = Manifest.permission.SET_DISPLAY_OFFSET;
private static final int UNPLUGGED = 0;
private static final String DUMPSYS_CMD_DISABLE_AOD_WHILE_PLUGGED = "disable_aod_while_plugged";
private final Context mContext;
private final WallpaperManager mWallpaperManager;
private final PowerManager mPowerManager;
private final BinderService mBinderService;
private final AmbientConfig mAmbientConfig;
private final BurnInProtector mBurnInProtector;
private DisplayManagerInternal mDisplayManager;
// The name of a watchface that we have forced TTW ON for
private String mForcedTtwOnWatchFace;
private Boolean mSavedDecomposableWatchface;
private boolean mInitialized = false;
private boolean mDisableAodWhilePlugged;
public WearDisplayService(Context context) {
this(context,
WallpaperManager.getInstance(context),
context.getSystemService(PowerManager.class));
}
@VisibleForTesting WearDisplayService(
Context context, WallpaperManager wallpaperManager, PowerManager powerManager) {
super(context);
mContext = context;
mWallpaperManager = wallpaperManager;
mPowerManager = powerManager;
mBinderService = new BinderService();
mAmbientConfig = new AmbientConfig(context.getContentResolver());
mDisableAodWhilePlugged = shouldDisableAODWhilePluggedFromConfig();
mBurnInProtector = BurnInProtector.create(context);
}
@Override
public void onStart() {
publishBinderService(TAG, mBinderService);
}
@Override
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
mDisplayManager = getLocalService(DisplayManagerInternal.class);
} else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
TheaterMode theaterMode = new TheaterMode(mContext);
// Monitor ambient-related settings.
onAmbientConfigChanged();
mAmbientConfig.addListener(this::onAmbientConfigChanged);
mAmbientConfig.register();
final IntentFilter intentFilter = new IntentFilter();
intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
intentFilter.addAction(Intent.ACTION_POWER_CONNECTED);
intentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);
mContext.registerReceiver(
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_POWER_CONNECTED)) {
onAmbientConfigChanged(/* plugged= */ true);
} else if (intent.getAction()
.equals(Intent.ACTION_POWER_DISCONNECTED)) {
onAmbientConfigChanged(/* plugged= */ false);
}
}
},
intentFilter);
mInitialized = true;
mBurnInProtector.init(mContext);
}
}
@VisibleForTesting final class BinderService extends IWearDisplayService.Stub {
@Override
@RequiresPermission(Manifest.permission.SET_DISPLAY_OFFSET)
public void setDisplayOffsets(int displayId, int x, int y) {
if (mContext.checkCallingOrSelfPermission(SET_DISPLAY_OFFSET)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires SET_DISPLAY_OFFSET permission.");
}
if (mDisplayManager != null) {
mDisplayManager.setDisplayOffsets(displayId, x, y);
}
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
final long ident = Binder.clearCallingIdentity();
try {
WearDisplayService.this.dump(fd, pw, args);
} catch (Throwable throwable) {
pw.println("caught exception while dumping " + throwable.getMessage());
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
@VisibleForTesting void onAmbientConfigChanged() {
onAmbientConfigChanged(isPlugged());
}
private void onAmbientConfigChanged(boolean plugged) {
boolean ambientEnabled = mAmbientConfig.isAmbientEnabled();
if (plugged && mDisableAodWhilePlugged) {
ambientEnabled = false;
}
boolean ambientTiltToWake = mAmbientConfig.isTiltToWake();
boolean ambientTiltToBright = mAmbientConfig.isUserTiltToBright();
boolean decomposableWatchface = mAmbientConfig.isWatchfaceDecomposable();
updateDoze(ambientEnabled, ambientTiltToBright, ambientTiltToWake, decomposableWatchface);
// Run this only if decomposable watch face has changed.
if (mSavedDecomposableWatchface == null
|| decomposableWatchface != mSavedDecomposableWatchface) {
checkWatchFaceChange(ambientTiltToBright, ambientTiltToWake, decomposableWatchface);
}
mSavedDecomposableWatchface = decomposableWatchface;
}
private void updateDoze(
boolean ambientEnabled,
boolean ambientTiltToBright,
boolean ambientTiltToWake,
boolean decomposableWatchface) {
if (Build.IS_DEBUGGABLE) {
Log.d(
TAG,
"Updating Ambient setting, TTB: "
+ ambientTiltToBright
+ ", TTW: "
+ ambientTiltToWake
+ ", decomposable: "
+ decomposableWatchface
+ ", AOD: "
+ ambientEnabled);
}
// We need to keep doze enabled when Power Saver Tilt / TTB is turned on
boolean shouldDozeBeEnabled =
ambientEnabled || (ambientTiltToBright && decomposableWatchface);
if (isDozeEnabled() != shouldDozeBeEnabled) {
long token = Binder.clearCallingIdentity();
try {
Settings.Secure.putInt(
mContext.getContentResolver(),
Settings.Secure.DOZE_ENABLED,
shouldDozeBeEnabled ? 1 : 0);
} finally {
Binder.restoreCallingIdentity(token);
}
if (mInitialized) {
Log.d(TAG, "Waking up on doze change");
mPowerManager.wakeUp(
SystemClock.uptimeMillis(),
PowerManager.WAKE_REASON_APPLICATION,
"onDozeChanged");
}
}
}
private void checkWatchFaceChange(
boolean ambientTiltToBright, boolean ambientTiltToWake, boolean decomposableWatchface) {
String currentWatchFace = getCurrentWatchFaceComponentString();
// During normal circumstances, currentWatchFace will never be null, check anyway
if (currentWatchFace != null) {
// If we switch to non-decomposable watch face, TTB was ON, and TTW was OFF,
// turn TTW ON
if (!decomposableWatchface && ambientTiltToBright && !ambientTiltToWake) {
Log.d(TAG, "Forcing TTW ON for non-decomposable watchface " + currentWatchFace);
mAmbientConfig.setTiltToWake(true);
mForcedTtwOnWatchFace = currentWatchFace;
// If we turned TTW ON with above logic for this watch face, but we now find
// that it is decomposable, turn TTW back OFF
} else if (decomposableWatchface && currentWatchFace.equals(mForcedTtwOnWatchFace)) {
Log.d(TAG, "Restoring TTW OFF for decomposable watch face" + currentWatchFace);
mAmbientConfig.setTiltToWake(false);
mForcedTtwOnWatchFace = "";
// If neither of above is applicable, reset the forced watch face value
} else {
mForcedTtwOnWatchFace = "";
}
} else {
Log.w(TAG, "Watch face info is null!");
}
}
@Nullable
private String getCurrentWatchFaceComponentString() {
WallpaperInfo wallpaperInfo = mWallpaperManager.getWallpaperInfo();
return wallpaperInfo == null ? null : wallpaperInfo.getComponent().flattenToString();
}
private boolean isDozeEnabled() {
return Settings.Secure.getInt(
mContext.getContentResolver(), Settings.Secure.DOZE_ENABLED, /* def= */ 1)
== 1;
}
/**
* Determines if WearDisplayService should disable AOD while the device is plugged in.
*
* @return false, unless specified otherwise with the config_disableAODWhilePlugged resource.
*/
private boolean shouldDisableAODWhilePluggedFromConfig() {
Resources wearableResources = WearResourceUtil.getWearableResources(mContext);
if (wearableResources != null) {
return wearableResources.getBoolean(config_disableAODWhilePlugged);
}
return false;
}
/**
* Determines if the device is plugged in. Note that this method does not check if the device
* is charging, because when battery defender is active the device may be plugged in but not
* actively charging.
*
* @return true if the device is plugged in, false if otherwise.
*/
private boolean isPlugged() {
Intent intentBatteryChanged =
mContext.registerReceiver(null,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
boolean returnValue = false;
if (intentBatteryChanged != null) {
returnValue =
intentBatteryChanged.getIntExtra(BatteryManager.EXTRA_PLUGGED, UNPLUGGED)
!= UNPLUGGED;
}
if (Build.IS_DEBUGGABLE) {
Log.d(TAG, "isPlugged: " + returnValue);
}
return returnValue;
}
@VisibleForTesting BinderService getBinderServiceInstance() {
return mBinderService;
}
@VisibleForTesting
void setDisableAodWhilePlugged(boolean disableAodWhilePlugged) {
mDisableAodWhilePlugged = disableAodWhilePlugged;
}
private void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
if (maybeExecuteDumpsysCommand(args, ipw)) {
return;
}
ipw.println("WearDisplayService");
ipw.increaseIndent();
ipw.println("mInitialized: " + mInitialized);
ipw.println("isPlugged: " + isPlugged());
ipw.println("mDisableAodWhilePlugged: " + mDisableAodWhilePlugged);
ipw.println("ambientEnabled: " + mAmbientConfig.isAmbientEnabled());
ipw.println("TTWEnabled: " + mAmbientConfig.isTiltToWake());
ipw.println("TTBEnabled: " + mAmbientConfig.isUserTiltToBright());
ipw.println("watchFaceDecomposable: " + mAmbientConfig.isWatchfaceDecomposable());
ipw.println("mSavedDecomposableWatchface: " + mSavedDecomposableWatchface);
ipw.println("mForcedTtwOnWatchFace: " + mForcedTtwOnWatchFace);
ipw.println("dozeEnabled: " + isDozeEnabled());
ipw.decreaseIndent();
}
private boolean maybeExecuteDumpsysCommand(String[] args, IndentingPrintWriter ipw) {
if (args.length == 0) {
return false;
}
try {
if (DUMPSYS_CMD_DISABLE_AOD_WHILE_PLUGGED.equals(args[0])) {
boolean val = Boolean.valueOf(args[1]);
if (mDisableAodWhilePlugged != val) {
mDisableAodWhilePlugged = val;
onAmbientConfigChanged();
}
ipw.println("Disable aod while plugged is set to " + mDisableAodWhilePlugged);
return true;
} else {
ipw.println("Unrecognized command \"" + args[0] + "\"");
}
} catch (Throwable t) {
String msg = "Failed to execute dumpsys command " + Arrays.toString(args);
Log.e(TAG, msg);
ipw.println(msg);
}
return false;
}
}