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.content.ContentResolver; 22 import android.content.Context; 23 import android.database.ContentObserver; 24 import android.net.Uri; 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.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 /** @hide */ 50 @Retention(RetentionPolicy.SOURCE) 51 @IntDef({ AUTO_MODE_DISABLED, AUTO_MODE_CUSTOM, AUTO_MODE_TWILIGHT }) 52 public @interface AutoMode {} 53 54 /** 55 * Auto mode value to prevent Night display from being automatically activated. It can still 56 * be activated manually via {@link #setActivated(boolean)}. 57 * 58 * @see #setAutoMode(int) 59 */ 60 public static final int AUTO_MODE_DISABLED = 0; 61 /** 62 * Auto mode value to automatically activate Night display at a specific start and end time. 63 * 64 * @see #setAutoMode(int) 65 * @see #setCustomStartTime(LocalTime) 66 * @see #setCustomEndTime(LocalTime) 67 */ 68 public static final int AUTO_MODE_CUSTOM = 1; 69 /** 70 * Auto mode value to automatically activate Night display from sunset to sunrise. 71 * 72 * @see #setAutoMode(int) 73 */ 74 public static final int AUTO_MODE_TWILIGHT = 2; 75 76 private final Context mContext; 77 private final int mUserId; 78 79 private final ContentObserver mContentObserver; 80 81 private Callback mCallback; 82 83 public NightDisplayController(@NonNull Context context) { 84 this(context, UserHandle.myUserId()); 85 } 86 87 public NightDisplayController(@NonNull Context context, int userId) { 88 mContext = context.getApplicationContext(); 89 mUserId = userId; 90 91 mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) { 92 @Override 93 public void onChange(boolean selfChange, Uri uri) { 94 super.onChange(selfChange, uri); 95 96 final String setting = uri == null ? null : uri.getLastPathSegment(); 97 if (setting != null) { 98 onSettingChanged(setting); 99 } 100 } 101 }; 102 } 103 104 /** 105 * Returns {@code true} when Night display is activated (the display is tinted red). 106 */ 107 public boolean isActivated() { 108 return Secure.getIntForUser(mContext.getContentResolver(), 109 Secure.NIGHT_DISPLAY_ACTIVATED, 0, mUserId) == 1; 110 } 111 112 /** 113 * Sets whether Night display should be activated. 114 * 115 * @param activated {@code true} if Night display should be activated 116 * @return {@code true} if the activated value was set successfully 117 */ 118 public boolean setActivated(boolean activated) { 119 return Secure.putIntForUser(mContext.getContentResolver(), 120 Secure.NIGHT_DISPLAY_ACTIVATED, activated ? 1 : 0, mUserId); 121 } 122 123 /** 124 * Returns the current auto mode value controlling when Night display will be automatically 125 * activated. One of {@link #AUTO_MODE_DISABLED}, {@link #AUTO_MODE_CUSTOM}, or 126 * {@link #AUTO_MODE_TWILIGHT}. 127 */ 128 public @AutoMode int getAutoMode() { 129 int autoMode = Secure.getIntForUser(mContext.getContentResolver(), 130 Secure.NIGHT_DISPLAY_AUTO_MODE, -1, mUserId); 131 if (autoMode == -1) { 132 if (DEBUG) { 133 Slog.d(TAG, "Using default value for setting: " + Secure.NIGHT_DISPLAY_AUTO_MODE); 134 } 135 autoMode = mContext.getResources().getInteger( 136 R.integer.config_defaultNightDisplayAutoMode); 137 } 138 139 if (autoMode != AUTO_MODE_DISABLED 140 && autoMode != AUTO_MODE_CUSTOM 141 && autoMode != AUTO_MODE_TWILIGHT) { 142 Slog.e(TAG, "Invalid autoMode: " + autoMode); 143 autoMode = AUTO_MODE_DISABLED; 144 } 145 146 return autoMode; 147 } 148 149 /** 150 * Sets the current auto mode value controlling when Night display will be automatically 151 * activated. One of {@link #AUTO_MODE_DISABLED}, {@link #AUTO_MODE_CUSTOM}, or 152 * {@link #AUTO_MODE_TWILIGHT}. 153 * 154 * @param autoMode the new auto mode to use 155 * @return {@code true} if new auto mode was set successfully 156 */ 157 public boolean setAutoMode(@AutoMode int autoMode) { 158 if (autoMode != AUTO_MODE_DISABLED 159 && autoMode != AUTO_MODE_CUSTOM 160 && autoMode != AUTO_MODE_TWILIGHT) { 161 throw new IllegalArgumentException("Invalid autoMode: " + autoMode); 162 } 163 164 return Secure.putIntForUser(mContext.getContentResolver(), 165 Secure.NIGHT_DISPLAY_AUTO_MODE, autoMode, mUserId); 166 } 167 168 /** 169 * Returns the local time when Night display will be automatically activated when using 170 * {@link #AUTO_MODE_CUSTOM}. 171 */ 172 public @NonNull LocalTime getCustomStartTime() { 173 int startTimeValue = Secure.getIntForUser(mContext.getContentResolver(), 174 Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, -1, mUserId); 175 if (startTimeValue == -1) { 176 if (DEBUG) { 177 Slog.d(TAG, "Using default value for setting: " 178 + Secure.NIGHT_DISPLAY_CUSTOM_START_TIME); 179 } 180 startTimeValue = mContext.getResources().getInteger( 181 R.integer.config_defaultNightDisplayCustomStartTime); 182 } 183 184 return LocalTime.valueOf(startTimeValue); 185 } 186 187 /** 188 * Sets the local time when Night display will be automatically activated when using 189 * {@link #AUTO_MODE_CUSTOM}. 190 * 191 * @param startTime the local time to automatically activate Night display 192 * @return {@code true} if the new custom start time was set successfully 193 */ 194 public boolean setCustomStartTime(@NonNull LocalTime startTime) { 195 if (startTime == null) { 196 throw new IllegalArgumentException("startTime cannot be null"); 197 } 198 return Secure.putIntForUser(mContext.getContentResolver(), 199 Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, startTime.toMillis(), mUserId); 200 } 201 202 /** 203 * Returns the local time when Night display will be automatically deactivated when using 204 * {@link #AUTO_MODE_CUSTOM}. 205 */ 206 public @NonNull LocalTime getCustomEndTime() { 207 int endTimeValue = Secure.getIntForUser(mContext.getContentResolver(), 208 Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, -1, mUserId); 209 if (endTimeValue == -1) { 210 if (DEBUG) { 211 Slog.d(TAG, "Using default value for setting: " 212 + Secure.NIGHT_DISPLAY_CUSTOM_END_TIME); 213 } 214 endTimeValue = mContext.getResources().getInteger( 215 R.integer.config_defaultNightDisplayCustomEndTime); 216 } 217 218 return LocalTime.valueOf(endTimeValue); 219 } 220 221 /** 222 * Sets the local time when Night display will be automatically deactivated when using 223 * {@link #AUTO_MODE_CUSTOM}. 224 * 225 * @param endTime the local time to automatically deactivate Night display 226 * @return {@code true} if the new custom end time was set successfully 227 */ 228 public boolean setCustomEndTime(@NonNull LocalTime endTime) { 229 if (endTime == null) { 230 throw new IllegalArgumentException("endTime cannot be null"); 231 } 232 return Secure.putIntForUser(mContext.getContentResolver(), 233 Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, endTime.toMillis(), mUserId); 234 } 235 236 private void onSettingChanged(@NonNull String setting) { 237 if (DEBUG) { 238 Slog.d(TAG, "onSettingChanged: " + setting); 239 } 240 241 if (mCallback != null) { 242 switch (setting) { 243 case Secure.NIGHT_DISPLAY_ACTIVATED: 244 mCallback.onActivated(isActivated()); 245 break; 246 case Secure.NIGHT_DISPLAY_AUTO_MODE: 247 mCallback.onAutoModeChanged(getAutoMode()); 248 break; 249 case Secure.NIGHT_DISPLAY_CUSTOM_START_TIME: 250 mCallback.onCustomStartTimeChanged(getCustomStartTime()); 251 break; 252 case Secure.NIGHT_DISPLAY_CUSTOM_END_TIME: 253 mCallback.onCustomEndTimeChanged(getCustomEndTime()); 254 break; 255 } 256 } 257 } 258 259 /** 260 * Register a callback to be invoked whenever the Night display settings are changed. 261 */ 262 public void setListener(Callback callback) { 263 final Callback oldCallback = mCallback; 264 if (oldCallback != callback) { 265 mCallback = callback; 266 267 if (callback == null) { 268 // Stop listening for changes now that there IS NOT a listener. 269 mContext.getContentResolver().unregisterContentObserver(mContentObserver); 270 } else if (oldCallback == null) { 271 // Start listening for changes now that there IS a listener. 272 final ContentResolver cr = mContext.getContentResolver(); 273 cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_ACTIVATED), 274 false /* notifyForDescendants */, mContentObserver, mUserId); 275 cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_AUTO_MODE), 276 false /* notifyForDescendants */, mContentObserver, mUserId); 277 cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_CUSTOM_START_TIME), 278 false /* notifyForDescendants */, mContentObserver, mUserId); 279 cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_CUSTOM_END_TIME), 280 false /* notifyForDescendants */, mContentObserver, mUserId); 281 } 282 } 283 } 284 285 /** 286 * Returns {@code true} if Night display is supported by the device. 287 */ 288 public static boolean isAvailable(Context context) { 289 return context.getResources().getBoolean(R.bool.config_nightDisplayAvailable); 290 } 291 292 /** 293 * A time without a time-zone or date. 294 */ 295 public static class LocalTime { 296 297 /** 298 * The hour of the day from 0 - 23. 299 */ 300 public final int hourOfDay; 301 /** 302 * The minute within the hour from 0 - 59. 303 */ 304 public final int minute; 305 306 public LocalTime(int hourOfDay, int minute) { 307 if (hourOfDay < 0 || hourOfDay > 23) { 308 throw new IllegalArgumentException("Invalid hourOfDay: " + hourOfDay); 309 } else if (minute < 0 || minute > 59) { 310 throw new IllegalArgumentException("Invalid minute: " + minute); 311 } 312 313 this.hourOfDay = hourOfDay; 314 this.minute = minute; 315 } 316 317 /** 318 * Returns the first date time corresponding to this local time that occurs before the 319 * provided date time. 320 * 321 * @param time the date time to compare against 322 * @return the prior date time corresponding to this local time 323 */ 324 public Calendar getDateTimeBefore(Calendar time) { 325 final Calendar c = Calendar.getInstance(); 326 c.set(Calendar.YEAR, time.get(Calendar.YEAR)); 327 c.set(Calendar.DAY_OF_YEAR, time.get(Calendar.DAY_OF_YEAR)); 328 329 c.set(Calendar.HOUR_OF_DAY, hourOfDay); 330 c.set(Calendar.MINUTE, minute); 331 c.set(Calendar.SECOND, 0); 332 c.set(Calendar.MILLISECOND, 0); 333 334 // Check if the local time has past, if so return the same time tomorrow. 335 if (c.after(time)) { 336 c.add(Calendar.DATE, -1); 337 } 338 339 return c; 340 } 341 342 /** 343 * Returns the first date time corresponding to this local time that occurs after the 344 * provided date time. 345 * 346 * @param time the date time to compare against 347 * @return the next date time corresponding to this local time 348 */ 349 public Calendar getDateTimeAfter(Calendar time) { 350 final Calendar c = Calendar.getInstance(); 351 c.set(Calendar.YEAR, time.get(Calendar.YEAR)); 352 c.set(Calendar.DAY_OF_YEAR, time.get(Calendar.DAY_OF_YEAR)); 353 354 c.set(Calendar.HOUR_OF_DAY, hourOfDay); 355 c.set(Calendar.MINUTE, minute); 356 c.set(Calendar.SECOND, 0); 357 c.set(Calendar.MILLISECOND, 0); 358 359 // Check if the local time has past, if so return the same time tomorrow. 360 if (c.before(time)) { 361 c.add(Calendar.DATE, 1); 362 } 363 364 return c; 365 } 366 367 /** 368 * Returns a local time corresponding the given number of milliseconds from midnight. 369 * 370 * @param millis the number of milliseconds from midnight 371 * @return the corresponding local time 372 */ 373 private static LocalTime valueOf(int millis) { 374 final int hourOfDay = (millis / 3600000) % 24; 375 final int minutes = (millis / 60000) % 60; 376 return new LocalTime(hourOfDay, minutes); 377 } 378 379 /** 380 * Returns the local time represented as milliseconds from midnight. 381 */ 382 private int toMillis() { 383 return hourOfDay * 3600000 + minute * 60000; 384 } 385 386 @Override 387 public String toString() { 388 return String.format(Locale.US, "%02d:%02d", hourOfDay, minute); 389 } 390 } 391 392 /** 393 * Callback invoked whenever the Night display settings are changed. 394 */ 395 public interface Callback { 396 /** 397 * Callback invoked when the activated state changes. 398 * 399 * @param activated {@code true} if Night display is activated 400 */ 401 default void onActivated(boolean activated) {} 402 /** 403 * Callback invoked when the auto mode changes. 404 * 405 * @param autoMode the auto mode to use 406 */ 407 default void onAutoModeChanged(int autoMode) {} 408 /** 409 * Callback invoked when the time to automatically activate Night display changes. 410 * 411 * @param startTime the local time to automatically activate Night display 412 */ 413 default void onCustomStartTimeChanged(LocalTime startTime) {} 414 /** 415 * Callback invoked when the time to automatically deactivate Night display changes. 416 * 417 * @param endTime the local time to automatically deactivate Night display 418 */ 419 default void onCustomEndTimeChanged(LocalTime endTime) {} 420 } 421 } 422