Justin Klaassen | 911e889 | 2016-06-21 18:24:24 -0700 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright (C) 2016 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.server.display; |
| 18 | |
| 19 | import android.app.AlarmManager; |
| 20 | import android.content.BroadcastReceiver; |
| 21 | import android.content.ContentResolver; |
| 22 | import android.content.Context; |
| 23 | import android.content.Intent; |
| 24 | import android.content.IntentFilter; |
| 25 | import android.os.Handler; |
| 26 | import android.os.Looper; |
| 27 | import android.os.UserHandle; |
| 28 | import android.provider.Settings.Secure; |
| 29 | import android.util.Slog; |
| 30 | |
| 31 | import com.android.internal.app.NightDisplayController; |
| 32 | import com.android.server.SystemService; |
| 33 | import com.android.server.twilight.TwilightListener; |
| 34 | import com.android.server.twilight.TwilightManager; |
| 35 | import com.android.server.twilight.TwilightState; |
| 36 | |
| 37 | import java.util.Calendar; |
| 38 | import java.util.TimeZone; |
| 39 | |
| 40 | /** |
| 41 | * Tints the display at night. |
| 42 | */ |
| 43 | public final class NightDisplayService extends SystemService |
| 44 | implements NightDisplayController.Callback { |
| 45 | |
| 46 | private static final String TAG = "NightDisplayService"; |
| 47 | private static final boolean DEBUG = false; |
| 48 | |
| 49 | /** |
| 50 | * Night mode ~= 3400 K. |
| 51 | */ |
| 52 | private static final String MATRIX_NIGHT = "1,0,0,0,0,.754,0,0,0,0,.516,0,0,0,0,1"; |
| 53 | |
| 54 | private int mCurrentUser = UserHandle.USER_NULL; |
| 55 | private boolean mBootCompleted; |
| 56 | |
| 57 | private NightDisplayController mController; |
| 58 | private Boolean mIsActivated; |
| 59 | private AutoMode mAutoMode; |
| 60 | |
| 61 | public NightDisplayService(Context context) { |
| 62 | super(context); |
| 63 | } |
| 64 | |
| 65 | @Override |
| 66 | public void onStart() { |
| 67 | // Nothing to publish. |
| 68 | } |
| 69 | |
| 70 | @Override |
| 71 | public void onStartUser(int userHandle) { |
| 72 | super.onStartUser(userHandle); |
| 73 | |
| 74 | // Register listeners for the new user. |
| 75 | if (mCurrentUser == UserHandle.USER_NULL) { |
| 76 | mCurrentUser = userHandle; |
| 77 | if (mBootCompleted) { |
| 78 | setUpNightMode(); |
| 79 | } |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | @Override |
| 84 | public void onSwitchUser(int userHandle) { |
| 85 | super.onSwitchUser(userHandle); |
| 86 | |
| 87 | // Unregister listeners for the old user. |
| 88 | if (mBootCompleted && mCurrentUser != UserHandle.USER_NULL) { |
| 89 | tearDownNightMode(); |
| 90 | } |
| 91 | |
| 92 | // Register listeners for the new user. |
| 93 | mCurrentUser = userHandle; |
| 94 | if (mBootCompleted) { |
| 95 | setUpNightMode(); |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | @Override |
| 100 | public void onStopUser(int userHandle) { |
| 101 | super.onStopUser(userHandle); |
| 102 | |
| 103 | // Unregister listeners for the old user. |
| 104 | if (mCurrentUser == userHandle) { |
| 105 | if (mBootCompleted) { |
| 106 | tearDownNightMode(); |
| 107 | } |
| 108 | mCurrentUser = UserHandle.USER_NULL; |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | @Override |
| 113 | public void onBootPhase(int phase) { |
| 114 | if (phase == PHASE_BOOT_COMPLETED) { |
| 115 | mBootCompleted = true; |
| 116 | |
| 117 | // Register listeners now that boot is complete. |
| 118 | if (mCurrentUser != UserHandle.USER_NULL) { |
| 119 | setUpNightMode(); |
| 120 | } |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | private void setUpNightMode() { |
| 125 | // Create a new controller for the current user and start listening for changes. |
| 126 | mController = new NightDisplayController(getContext(), mCurrentUser); |
| 127 | mController.setListener(this); |
| 128 | |
| 129 | // Initialize the current auto mode. |
| 130 | onAutoModeChanged(mController.getAutoMode()); |
| 131 | |
| 132 | // Force the initialization current activated state. |
| 133 | if (mIsActivated == null) { |
| 134 | onActivated(mController.isActivated()); |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | private void tearDownNightMode() { |
| 139 | mController.setListener(null); |
| 140 | |
| 141 | if (mAutoMode != null) { |
| 142 | mAutoMode.onStop(); |
| 143 | mAutoMode = null; |
| 144 | } |
| 145 | |
| 146 | mIsActivated = null; |
| 147 | mController = null; |
| 148 | } |
| 149 | |
| 150 | @Override |
| 151 | public void onActivated(boolean activated) { |
| 152 | if (mIsActivated == null || mIsActivated != activated) { |
| 153 | Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display"); |
| 154 | |
| 155 | mIsActivated = activated; |
| 156 | |
| 157 | if (mAutoMode != null) { |
| 158 | mAutoMode.onActivated(activated); |
| 159 | } |
| 160 | |
| 161 | // Update the current color matrix. |
| 162 | final ContentResolver cr = getContext().getContentResolver(); |
| 163 | Secure.putStringForUser(cr, Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX, |
| 164 | activated ? MATRIX_NIGHT : null, mCurrentUser); |
| 165 | } |
| 166 | } |
| 167 | |
| 168 | @Override |
| 169 | public void onAutoModeChanged(int autoMode) { |
| 170 | if (mAutoMode != null) { |
| 171 | mAutoMode.onStop(); |
| 172 | mAutoMode = null; |
| 173 | } |
| 174 | |
| 175 | if (autoMode == NightDisplayController.AUTO_MODE_CUSTOM) { |
| 176 | mAutoMode = new CustomAutoMode(); |
| 177 | } else if (autoMode == NightDisplayController.AUTO_MODE_TWILIGHT) { |
| 178 | mAutoMode = new TwilightAutoMode(); |
| 179 | } |
| 180 | |
| 181 | if (mAutoMode != null) { |
| 182 | mAutoMode.onStart(); |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | @Override |
| 187 | public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) { |
| 188 | if (mAutoMode != null) { |
| 189 | mAutoMode.onCustomStartTimeChanged(startTime); |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | @Override |
| 194 | public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) { |
| 195 | if (mAutoMode != null) { |
| 196 | mAutoMode.onCustomEndTimeChanged(endTime); |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | private abstract class AutoMode implements NightDisplayController.Callback { |
| 201 | public abstract void onStart(); |
| 202 | public abstract void onStop(); |
| 203 | } |
| 204 | |
| 205 | private class CustomAutoMode extends AutoMode implements AlarmManager.OnAlarmListener { |
| 206 | |
| 207 | private final AlarmManager mAlarmManager; |
| 208 | private final BroadcastReceiver mTimeChangedReceiver; |
| 209 | |
| 210 | private NightDisplayController.LocalTime mStartTime; |
| 211 | private NightDisplayController.LocalTime mEndTime; |
| 212 | |
| 213 | private Calendar mLastActivatedTime; |
| 214 | |
| 215 | public CustomAutoMode() { |
| 216 | mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); |
| 217 | mTimeChangedReceiver = new BroadcastReceiver() { |
| 218 | @Override |
| 219 | public void onReceive(Context context, Intent intent) { |
| 220 | updateActivated(); |
| 221 | } |
| 222 | }; |
| 223 | } |
| 224 | |
| 225 | private void updateActivated() { |
| 226 | final Calendar now = Calendar.getInstance(); |
| 227 | final Calendar startTime = mStartTime.getDateTimeBefore(now); |
| 228 | final Calendar endTime = mEndTime.getDateTimeAfter(startTime); |
| 229 | final boolean activated = now.before(endTime); |
| 230 | |
| 231 | boolean setActivated = mIsActivated == null || mLastActivatedTime == null; |
| 232 | if (!setActivated && mIsActivated != activated) { |
| 233 | final TimeZone currentTimeZone = now.getTimeZone(); |
| 234 | if (!currentTimeZone.equals(mLastActivatedTime.getTimeZone())) { |
| 235 | final int year = mLastActivatedTime.get(Calendar.YEAR); |
| 236 | final int dayOfYear = mLastActivatedTime.get(Calendar.DAY_OF_YEAR); |
| 237 | final int hourOfDay = mLastActivatedTime.get(Calendar.HOUR_OF_DAY); |
| 238 | final int minute = mLastActivatedTime.get(Calendar.MINUTE); |
| 239 | |
| 240 | mLastActivatedTime.setTimeZone(currentTimeZone); |
| 241 | mLastActivatedTime.set(Calendar.YEAR, year); |
| 242 | mLastActivatedTime.set(Calendar.DAY_OF_YEAR, dayOfYear); |
| 243 | mLastActivatedTime.set(Calendar.HOUR_OF_DAY, hourOfDay); |
| 244 | mLastActivatedTime.set(Calendar.MINUTE, minute); |
| 245 | } |
| 246 | |
| 247 | if (mIsActivated) { |
| 248 | setActivated = now.before(mStartTime.getDateTimeBefore(mLastActivatedTime)) |
| 249 | || now.after(mEndTime.getDateTimeAfter(mLastActivatedTime)); |
| 250 | } else { |
| 251 | setActivated = now.before(mEndTime.getDateTimeBefore(mLastActivatedTime)) |
| 252 | || now.after(mStartTime.getDateTimeAfter(mLastActivatedTime)); |
| 253 | } |
| 254 | } |
| 255 | |
| 256 | if (setActivated) { |
| 257 | mController.setActivated(activated); |
| 258 | } |
| 259 | updateNextAlarm(); |
| 260 | } |
| 261 | |
| 262 | private void updateNextAlarm() { |
| 263 | if (mIsActivated != null) { |
| 264 | final Calendar now = Calendar.getInstance(); |
| 265 | final Calendar next = mIsActivated ? mEndTime.getDateTimeAfter(now) |
| 266 | : mStartTime.getDateTimeAfter(now); |
| 267 | mAlarmManager.setExact(AlarmManager.RTC, next.getTimeInMillis(), TAG, this, null); |
| 268 | } |
| 269 | } |
| 270 | |
| 271 | @Override |
| 272 | public void onStart() { |
| 273 | final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED); |
| 274 | intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED); |
| 275 | getContext().registerReceiver(mTimeChangedReceiver, intentFilter); |
| 276 | |
| 277 | mStartTime = mController.getCustomStartTime(); |
| 278 | mEndTime = mController.getCustomEndTime(); |
| 279 | |
| 280 | // Force an update to initialize state. |
| 281 | updateActivated(); |
| 282 | } |
| 283 | |
| 284 | @Override |
| 285 | public void onStop() { |
| 286 | getContext().unregisterReceiver(mTimeChangedReceiver); |
| 287 | |
| 288 | mAlarmManager.cancel(this); |
| 289 | mLastActivatedTime = null; |
| 290 | } |
| 291 | |
| 292 | @Override |
| 293 | public void onActivated(boolean activated) { |
| 294 | mLastActivatedTime = Calendar.getInstance(); |
| 295 | updateNextAlarm(); |
| 296 | } |
| 297 | |
| 298 | @Override |
| 299 | public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) { |
| 300 | mStartTime = startTime; |
| 301 | mLastActivatedTime = null; |
| 302 | updateActivated(); |
| 303 | } |
| 304 | |
| 305 | @Override |
| 306 | public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) { |
| 307 | mEndTime = endTime; |
| 308 | mLastActivatedTime = null; |
| 309 | updateActivated(); |
| 310 | } |
| 311 | |
| 312 | @Override |
| 313 | public void onAlarm() { |
| 314 | if (DEBUG) Slog.d(TAG, "onAlarm"); |
| 315 | updateActivated(); |
| 316 | } |
| 317 | } |
| 318 | |
| 319 | private class TwilightAutoMode extends AutoMode implements TwilightListener { |
| 320 | |
| 321 | private final TwilightManager mTwilightManager; |
| 322 | private final Handler mHandler; |
| 323 | |
| 324 | private boolean mIsNight; |
| 325 | |
| 326 | public TwilightAutoMode() { |
| 327 | mTwilightManager = getLocalService(TwilightManager.class); |
| 328 | mHandler = new Handler(Looper.getMainLooper()); |
| 329 | } |
| 330 | |
| 331 | private void updateActivated() { |
| 332 | final TwilightState state = mTwilightManager.getCurrentState(); |
| 333 | final boolean isNight = state != null && state.isNight(); |
| 334 | if (mIsNight != isNight) { |
| 335 | mIsNight = isNight; |
| 336 | |
| 337 | if (mIsActivated == null || mIsActivated != isNight) { |
| 338 | mController.setActivated(isNight); |
| 339 | } |
| 340 | } |
| 341 | } |
| 342 | |
| 343 | @Override |
| 344 | public void onStart() { |
| 345 | mTwilightManager.registerListener(this, mHandler); |
| 346 | |
| 347 | // Force an update to initialize state. |
| 348 | updateActivated(); |
| 349 | } |
| 350 | |
| 351 | @Override |
| 352 | public void onStop() { |
| 353 | mTwilightManager.unregisterListener(this); |
| 354 | } |
| 355 | |
| 356 | @Override |
| 357 | public void onTwilightStateChanged() { |
| 358 | if (DEBUG) Slog.d(TAG, "onTwilightStateChanged"); |
| 359 | updateActivated(); |
| 360 | } |
| 361 | } |
| 362 | } |