Home | History | Annotate | Download | only in app
      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