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.internal.app; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.app.ActivityManager; 22 import android.content.ContentResolver; 23 import android.content.Context; 24 import android.database.ContentObserver; 25 import android.net.Uri; 26 import android.os.Handler; 27 import android.os.Looper; 28 import android.provider.Settings.Secure; 29 import android.util.Slog; 30 31 import com.android.internal.R; 32 33 import java.lang.annotation.Retention; 34 import java.lang.annotation.RetentionPolicy; 35 import java.util.Calendar; 36 import java.util.Locale; 37 38 /** 39 * Controller for managing Night display settings. 40 * <p/> 41 * Night display tints your screen red at night. This makes it easier to look at your screen in 42 * dim light and may help you fall asleep more easily. 43 */ 44 public final class NightDisplayController { 45 46 private static final String TAG = "NightDisplayController"; 47 private static final boolean DEBUG = false; 48 49 @Retention(RetentionPolicy.SOURCE) 50 @IntDef({ AUTO_MODE_DISABLED, AUTO_MODE_CUSTOM, AUTO_MODE_TWILIGHT }) 51 public @interface AutoMode {} 52 53 /** 54 * Auto mode value to prevent Night display from being automatically activated. It can still 55 * be activated manually via {@link #setActivated(boolean)}. 56 * 57 * @see #setAutoMode(int) 58 */ 59 public static final int AUTO_MODE_DISABLED = 0; 60 /** 61 * Auto mode value to automatically activate Night display at a specific start and end time. 62 * 63 * @see #setAutoMode(int) 64 * @see #setCustomStartTime(LocalTime) 65 * @see #setCustomEndTime(LocalTime) 66 */ 67 public static final int AUTO_MODE_CUSTOM = 1; 68 /** 69 * Auto mode value to automatically activate Night display from sunset to sunrise. 70 * 71 * @see #setAutoMode(int) 72 */ 73 public static final int AUTO_MODE_TWILIGHT = 2; 74 75 private final Context mContext; 76 private final int mUserId; 77 78 private final ContentObserver mContentObserver; 79 80 private Callback mCallback; 81 NightDisplayController(@onNull Context context)82 public NightDisplayController(@NonNull Context context) { 83 this(context, ActivityManager.getCurrentUser()); 84 } 85 NightDisplayController(@onNull Context context, int userId)86 public NightDisplayController(@NonNull Context context, int userId) { 87 mContext = context.getApplicationContext(); 88 mUserId = userId; 89 90 mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) { 91 @Override 92 public void onChange(boolean selfChange, Uri uri) { 93 super.onChange(selfChange, uri); 94 95 final String setting = uri == null ? null : uri.getLastPathSegment(); 96 if (setting != null) { 97 onSettingChanged(setting); 98 } 99 } 100 }; 101 } 102 103 /** 104 * Returns {@code true} when Night display is activated (the display is tinted red). 105 */ isActivated()106 public boolean isActivated() { 107 return Secure.getIntForUser(mContext.getContentResolver(), 108 Secure.NIGHT_DISPLAY_ACTIVATED, 0, mUserId) == 1; 109 } 110 111 /** 112 * Sets whether Night display should be activated. This also sets the last activated time. 113 * 114 * @param activated {@code true} if Night display should be activated 115 * @return {@code true} if the activated value was set successfully 116 */ setActivated(boolean activated)117 public boolean setActivated(boolean activated) { 118 if (isActivated() != activated) { 119 Secure.putLongForUser(mContext.getContentResolver(), 120 Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, System.currentTimeMillis(), 121 mUserId); 122 } 123 return Secure.putIntForUser(mContext.getContentResolver(), 124 Secure.NIGHT_DISPLAY_ACTIVATED, activated ? 1 : 0, mUserId); 125 } 126 127 /** 128 * Returns the time when Night display's activation state last changed, or {@code null} if it 129 * has never been changed. 130 */ getLastActivatedTime()131 public Calendar getLastActivatedTime() { 132 final ContentResolver cr = mContext.getContentResolver(); 133 final long lastActivatedTimeMillis = Secure.getLongForUser( 134 cr, Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, -1, mUserId); 135 if (lastActivatedTimeMillis < 0) { 136 return null; 137 } 138 139 final Calendar lastActivatedTime = Calendar.getInstance(); 140 lastActivatedTime.setTimeInMillis(lastActivatedTimeMillis); 141 return lastActivatedTime; 142 } 143 144 /** 145 * Returns the current auto mode value controlling when Night display will be automatically 146 * activated. One of {@link #AUTO_MODE_DISABLED}, {@link #AUTO_MODE_CUSTOM}, or 147 * {@link #AUTO_MODE_TWILIGHT}. 148 */ getAutoMode()149 public @AutoMode int getAutoMode() { 150 int autoMode = Secure.getIntForUser(mContext.getContentResolver(), 151 Secure.NIGHT_DISPLAY_AUTO_MODE, -1, mUserId); 152 if (autoMode == -1) { 153 if (DEBUG) { 154 Slog.d(TAG, "Using default value for setting: " + Secure.NIGHT_DISPLAY_AUTO_MODE); 155 } 156 autoMode = mContext.getResources().getInteger( 157 R.integer.config_defaultNightDisplayAutoMode); 158 } 159 160 if (autoMode != AUTO_MODE_DISABLED 161 && autoMode != AUTO_MODE_CUSTOM 162 && autoMode != AUTO_MODE_TWILIGHT) { 163 Slog.e(TAG, "Invalid autoMode: " + autoMode); 164 autoMode = AUTO_MODE_DISABLED; 165 } 166 167 return autoMode; 168 } 169 170 /** 171 * Sets the current auto mode value controlling when Night display will be automatically 172 * activated. One of {@link #AUTO_MODE_DISABLED}, {@link #AUTO_MODE_CUSTOM}, or 173 * {@link #AUTO_MODE_TWILIGHT}. 174 * 175 * @param autoMode the new auto mode to use 176 * @return {@code true} if new auto mode was set successfully 177 */ setAutoMode(@utoMode int autoMode)178 public boolean setAutoMode(@AutoMode int autoMode) { 179 if (autoMode != AUTO_MODE_DISABLED 180 && autoMode != AUTO_MODE_CUSTOM 181 && autoMode != AUTO_MODE_TWILIGHT) { 182 throw new IllegalArgumentException("Invalid autoMode: " + autoMode); 183 } 184 185 return Secure.putIntForUser(mContext.getContentResolver(), 186 Secure.NIGHT_DISPLAY_AUTO_MODE, autoMode, mUserId); 187 } 188 189 /** 190 * Returns the local time when Night display will be automatically activated when using 191 * {@link #AUTO_MODE_CUSTOM}. 192 */ getCustomStartTime()193 public @NonNull LocalTime getCustomStartTime() { 194 int startTimeValue = Secure.getIntForUser(mContext.getContentResolver(), 195 Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, -1, mUserId); 196 if (startTimeValue == -1) { 197 if (DEBUG) { 198 Slog.d(TAG, "Using default value for setting: " 199 + Secure.NIGHT_DISPLAY_CUSTOM_START_TIME); 200 } 201 startTimeValue = mContext.getResources().getInteger( 202 R.integer.config_defaultNightDisplayCustomStartTime); 203 } 204 205 return LocalTime.valueOf(startTimeValue); 206 } 207 208 /** 209 * Sets the local time when Night display will be automatically activated when using 210 * {@link #AUTO_MODE_CUSTOM}. 211 * 212 * @param startTime the local time to automatically activate Night display 213 * @return {@code true} if the new custom start time was set successfully 214 */ setCustomStartTime(@onNull LocalTime startTime)215 public boolean setCustomStartTime(@NonNull LocalTime startTime) { 216 if (startTime == null) { 217 throw new IllegalArgumentException("startTime cannot be null"); 218 } 219 return Secure.putIntForUser(mContext.getContentResolver(), 220 Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, startTime.toMillis(), mUserId); 221 } 222 223 /** 224 * Returns the local time when Night display will be automatically deactivated when using 225 * {@link #AUTO_MODE_CUSTOM}. 226 */ getCustomEndTime()227 public @NonNull LocalTime getCustomEndTime() { 228 int endTimeValue = Secure.getIntForUser(mContext.getContentResolver(), 229 Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, -1, mUserId); 230 if (endTimeValue == -1) { 231 if (DEBUG) { 232 Slog.d(TAG, "Using default value for setting: " 233 + Secure.NIGHT_DISPLAY_CUSTOM_END_TIME); 234 } 235 endTimeValue = mContext.getResources().getInteger( 236 R.integer.config_defaultNightDisplayCustomEndTime); 237 } 238 239 return LocalTime.valueOf(endTimeValue); 240 } 241 242 /** 243 * Sets the local time when Night display will be automatically deactivated when using 244 * {@link #AUTO_MODE_CUSTOM}. 245 * 246 * @param endTime the local time to automatically deactivate Night display 247 * @return {@code true} if the new custom end time was set successfully 248 */ setCustomEndTime(@onNull LocalTime endTime)249 public boolean setCustomEndTime(@NonNull LocalTime endTime) { 250 if (endTime == null) { 251 throw new IllegalArgumentException("endTime cannot be null"); 252 } 253 return Secure.putIntForUser(mContext.getContentResolver(), 254 Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, endTime.toMillis(), mUserId); 255 } 256 257 /** 258 * Returns the color temperature (in Kelvin) to tint the display when activated. 259 */ getColorTemperature()260 public int getColorTemperature() { 261 int colorTemperature = Secure.getIntForUser(mContext.getContentResolver(), 262 Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, -1, mUserId); 263 if (colorTemperature == -1) { 264 if (DEBUG) { 265 Slog.d(TAG, "Using default value for setting: " 266 + Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE); 267 } 268 colorTemperature = getDefaultColorTemperature(); 269 } 270 final int minimumTemperature = getMinimumColorTemperature(); 271 final int maximumTemperature = getMaximumColorTemperature(); 272 if (colorTemperature < minimumTemperature) { 273 colorTemperature = minimumTemperature; 274 } else if (colorTemperature > maximumTemperature) { 275 colorTemperature = maximumTemperature; 276 } 277 278 return colorTemperature; 279 } 280 281 /** 282 * Sets the current temperature. 283 * 284 * @param colorTemperature the temperature, in Kelvin. 285 * @return {@code true} if new temperature was set successfully. 286 */ setColorTemperature(int colorTemperature)287 public boolean setColorTemperature(int colorTemperature) { 288 return Secure.putIntForUser(mContext.getContentResolver(), 289 Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, colorTemperature, mUserId); 290 } 291 292 /** 293 * Returns the minimum allowed color temperature (in Kelvin) to tint the display when activated. 294 */ getMinimumColorTemperature()295 public int getMinimumColorTemperature() { 296 return mContext.getResources().getInteger( 297 R.integer.config_nightDisplayColorTemperatureMin); 298 } 299 300 /** 301 * Returns the maximum allowed color temperature (in Kelvin) to tint the display when activated. 302 */ getMaximumColorTemperature()303 public int getMaximumColorTemperature() { 304 return mContext.getResources().getInteger( 305 R.integer.config_nightDisplayColorTemperatureMax); 306 } 307 308 /** 309 * Returns the default color temperature (in Kelvin) to tint the display when activated. 310 */ getDefaultColorTemperature()311 public int getDefaultColorTemperature() { 312 return mContext.getResources().getInteger( 313 R.integer.config_nightDisplayColorTemperatureDefault); 314 } 315 onSettingChanged(@onNull String setting)316 private void onSettingChanged(@NonNull String setting) { 317 if (DEBUG) { 318 Slog.d(TAG, "onSettingChanged: " + setting); 319 } 320 321 if (mCallback != null) { 322 switch (setting) { 323 case Secure.NIGHT_DISPLAY_ACTIVATED: 324 mCallback.onActivated(isActivated()); 325 break; 326 case Secure.NIGHT_DISPLAY_AUTO_MODE: 327 mCallback.onAutoModeChanged(getAutoMode()); 328 break; 329 case Secure.NIGHT_DISPLAY_CUSTOM_START_TIME: 330 mCallback.onCustomStartTimeChanged(getCustomStartTime()); 331 break; 332 case Secure.NIGHT_DISPLAY_CUSTOM_END_TIME: 333 mCallback.onCustomEndTimeChanged(getCustomEndTime()); 334 break; 335 case Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE: 336 mCallback.onColorTemperatureChanged(getColorTemperature()); 337 break; 338 } 339 } 340 } 341 342 /** 343 * Register a callback to be invoked whenever the Night display settings are changed. 344 */ setListener(Callback callback)345 public void setListener(Callback callback) { 346 final Callback oldCallback = mCallback; 347 if (oldCallback != callback) { 348 mCallback = callback; 349 350 if (callback == null) { 351 // Stop listening for changes now that there IS NOT a listener. 352 mContext.getContentResolver().unregisterContentObserver(mContentObserver); 353 } else if (oldCallback == null) { 354 // Start listening for changes now that there IS a listener. 355 final ContentResolver cr = mContext.getContentResolver(); 356 cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_ACTIVATED), 357 false /* notifyForDescendants */, mContentObserver, mUserId); 358 cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_AUTO_MODE), 359 false /* notifyForDescendants */, mContentObserver, mUserId); 360 cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_CUSTOM_START_TIME), 361 false /* notifyForDescendants */, mContentObserver, mUserId); 362 cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_CUSTOM_END_TIME), 363 false /* notifyForDescendants */, mContentObserver, mUserId); 364 cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE), 365 false /* notifyForDescendants */, mContentObserver, mUserId); 366 } 367 } 368 } 369 370 /** 371 * Returns {@code true} if Night display is supported by the device. 372 */ isAvailable(Context context)373 public static boolean isAvailable(Context context) { 374 return context.getResources().getBoolean(R.bool.config_nightDisplayAvailable); 375 } 376 377 /** 378 * A time without a time-zone or date. 379 */ 380 public static class LocalTime { 381 382 /** 383 * The hour of the day from 0 - 23. 384 */ 385 public final int hourOfDay; 386 /** 387 * The minute within the hour from 0 - 59. 388 */ 389 public final int minute; 390 LocalTime(int hourOfDay, int minute)391 public LocalTime(int hourOfDay, int minute) { 392 if (hourOfDay < 0 || hourOfDay > 23) { 393 throw new IllegalArgumentException("Invalid hourOfDay: " + hourOfDay); 394 } else if (minute < 0 || minute > 59) { 395 throw new IllegalArgumentException("Invalid minute: " + minute); 396 } 397 398 this.hourOfDay = hourOfDay; 399 this.minute = minute; 400 } 401 402 /** 403 * Returns the first date time corresponding to this local time that occurs before the 404 * provided date time. 405 * 406 * @param time the date time to compare against 407 * @return the prior date time corresponding to this local time 408 */ getDateTimeBefore(Calendar time)409 public Calendar getDateTimeBefore(Calendar time) { 410 final Calendar c = Calendar.getInstance(); 411 c.set(Calendar.YEAR, time.get(Calendar.YEAR)); 412 c.set(Calendar.DAY_OF_YEAR, time.get(Calendar.DAY_OF_YEAR)); 413 414 c.set(Calendar.HOUR_OF_DAY, hourOfDay); 415 c.set(Calendar.MINUTE, minute); 416 c.set(Calendar.SECOND, 0); 417 c.set(Calendar.MILLISECOND, 0); 418 419 // Check if the local time has past, if so return the same time tomorrow. 420 if (c.after(time)) { 421 c.add(Calendar.DATE, -1); 422 } 423 424 return c; 425 } 426 427 /** 428 * Returns the first date time corresponding to this local time that occurs after the 429 * provided date time. 430 * 431 * @param time the date time to compare against 432 * @return the next date time corresponding to this local time 433 */ getDateTimeAfter(Calendar time)434 public Calendar getDateTimeAfter(Calendar time) { 435 final Calendar c = Calendar.getInstance(); 436 c.set(Calendar.YEAR, time.get(Calendar.YEAR)); 437 c.set(Calendar.DAY_OF_YEAR, time.get(Calendar.DAY_OF_YEAR)); 438 439 c.set(Calendar.HOUR_OF_DAY, hourOfDay); 440 c.set(Calendar.MINUTE, minute); 441 c.set(Calendar.SECOND, 0); 442 c.set(Calendar.MILLISECOND, 0); 443 444 // Check if the local time has past, if so return the same time tomorrow. 445 if (c.before(time)) { 446 c.add(Calendar.DATE, 1); 447 } 448 449 return c; 450 } 451 452 /** 453 * Returns a local time corresponding the given number of milliseconds from midnight. 454 * 455 * @param millis the number of milliseconds from midnight 456 * @return the corresponding local time 457 */ valueOf(int millis)458 private static LocalTime valueOf(int millis) { 459 final int hourOfDay = (millis / 3600000) % 24; 460 final int minutes = (millis / 60000) % 60; 461 return new LocalTime(hourOfDay, minutes); 462 } 463 464 /** 465 * Returns the local time represented as milliseconds from midnight. 466 */ toMillis()467 private int toMillis() { 468 return hourOfDay * 3600000 + minute * 60000; 469 } 470 471 @Override toString()472 public String toString() { 473 return String.format(Locale.US, "%02d:%02d", hourOfDay, minute); 474 } 475 } 476 477 /** 478 * Callback invoked whenever the Night display settings are changed. 479 */ 480 public interface Callback { 481 /** 482 * Callback invoked when the activated state changes. 483 * 484 * @param activated {@code true} if Night display is activated 485 */ onActivated(boolean activated)486 default void onActivated(boolean activated) {} 487 /** 488 * Callback invoked when the auto mode changes. 489 * 490 * @param autoMode the auto mode to use 491 */ onAutoModeChanged(int autoMode)492 default void onAutoModeChanged(int autoMode) {} 493 /** 494 * Callback invoked when the time to automatically activate Night display changes. 495 * 496 * @param startTime the local time to automatically activate Night display 497 */ onCustomStartTimeChanged(LocalTime startTime)498 default void onCustomStartTimeChanged(LocalTime startTime) {} 499 /** 500 * Callback invoked when the time to automatically deactivate Night display changes. 501 * 502 * @param endTime the local time to automatically deactivate Night display 503 */ onCustomEndTimeChanged(LocalTime endTime)504 default void onCustomEndTimeChanged(LocalTime endTime) {} 505 506 /** 507 * Callback invoked when the color temperature changes. 508 * 509 * @param colorTemperature the color temperature to tint the screen 510 */ onColorTemperatureChanged(int colorTemperature)511 default void onColorTemperatureChanged(int colorTemperature) {} 512 } 513 } 514