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.systemui.qs.tiles;
18 
19 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_QS_MODE;
20 
21 import android.content.Intent;
22 import android.hardware.display.ColorDisplayManager;
23 import android.hardware.display.NightDisplayListener;
24 import android.metrics.LogMaker;
25 import android.os.Handler;
26 import android.os.Looper;
27 import android.provider.Settings;
28 import android.service.quicksettings.Tile;
29 import android.text.TextUtils;
30 import android.util.Log;
31 import android.view.View;
32 import android.widget.Switch;
33 
34 import androidx.annotation.Nullable;
35 import androidx.annotation.StringRes;
36 
37 import com.android.internal.logging.MetricsLogger;
38 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
39 import com.android.systemui.R;
40 import com.android.systemui.dagger.NightDisplayListenerModule;
41 import com.android.systemui.dagger.qualifiers.Background;
42 import com.android.systemui.dagger.qualifiers.Main;
43 import com.android.systemui.plugins.ActivityStarter;
44 import com.android.systemui.plugins.FalsingManager;
45 import com.android.systemui.plugins.qs.QSTile.BooleanState;
46 import com.android.systemui.plugins.statusbar.StatusBarStateController;
47 import com.android.systemui.qs.QSHost;
48 import com.android.systemui.qs.QsEventLogger;
49 import com.android.systemui.qs.logging.QSLogger;
50 import com.android.systemui.qs.tileimpl.QSTileImpl;
51 import com.android.systemui.statusbar.policy.LocationController;
52 
53 import java.text.DateFormat;
54 import java.time.LocalTime;
55 import java.time.format.DateTimeFormatter;
56 import java.util.Calendar;
57 import java.util.TimeZone;
58 
59 import javax.inject.Inject;
60 
61 /** Quick settings tile: Night display **/
62 public class NightDisplayTile extends QSTileImpl<BooleanState> implements
63         NightDisplayListener.Callback {
64 
65     public static final String TILE_SPEC = "night";
66 
67     /**
68      * Pattern for {@link java.time.format.DateTimeFormatter} used to approximate the time to the
69      * nearest hour and add on the AM/PM indicator.
70      */
71     private static final String PATTERN_HOUR = "h a";
72     private static final String PATTERN_HOUR_MINUTE = "h:mm a";
73     private static final String PATTERN_HOUR_NINUTE_24 = "HH:mm";
74 
75     private ColorDisplayManager mManager;
76     private final LocationController mLocationController;
77     private final NightDisplayListenerModule.Builder mNightDisplayListenerBuilder;
78     private NightDisplayListener mListener;
79     private boolean mIsListening;
80 
81     @Inject
NightDisplayTile( QSHost host, QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, MetricsLogger metricsLogger, StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, LocationController locationController, ColorDisplayManager colorDisplayManager, NightDisplayListenerModule.Builder nightDisplayListenerBuilder )82     public NightDisplayTile(
83             QSHost host,
84             QsEventLogger uiEventLogger,
85             @Background Looper backgroundLooper,
86             @Main Handler mainHandler,
87             FalsingManager falsingManager,
88             MetricsLogger metricsLogger,
89             StatusBarStateController statusBarStateController,
90             ActivityStarter activityStarter,
91             QSLogger qsLogger,
92             LocationController locationController,
93             ColorDisplayManager colorDisplayManager,
94             NightDisplayListenerModule.Builder nightDisplayListenerBuilder
95     ) {
96         super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
97                 statusBarStateController, activityStarter, qsLogger);
98         mLocationController = locationController;
99         mManager = colorDisplayManager;
100         mNightDisplayListenerBuilder = nightDisplayListenerBuilder;
101         mListener = mNightDisplayListenerBuilder.setUser(host.getUserContext().getUserId()).build();
102     }
103 
104     @Override
isAvailable()105     public boolean isAvailable() {
106         return ColorDisplayManager.isNightDisplayAvailable(mContext);
107     }
108 
109     @Override
newTileState()110     public BooleanState newTileState() {
111         return new BooleanState();
112     }
113 
114     @Override
handleClick(@ullable View view)115     protected void handleClick(@Nullable View view) {
116         // Enroll in forced auto mode if eligible.
117         if ("1".equals(Settings.Global.getString(mContext.getContentResolver(),
118                 Settings.Global.NIGHT_DISPLAY_FORCED_AUTO_MODE_AVAILABLE))
119                 && mManager.getNightDisplayAutoModeRaw() == -1) {
120             mManager.setNightDisplayAutoMode(ColorDisplayManager.AUTO_MODE_CUSTOM_TIME);
121             Log.i("NightDisplayTile", "Enrolled in forced night display auto mode");
122         }
123 
124         // Change current activation state.
125         final boolean activated = !mState.value;
126         mManager.setNightDisplayActivated(activated);
127     }
128 
129     @Override
handleUserSwitch(int newUserId)130     protected void handleUserSwitch(int newUserId) {
131         // Stop listening to the old controller.
132         if (mIsListening) {
133             mListener.setCallback(null);
134         }
135 
136         mManager = getHost().getUserContext().getSystemService(ColorDisplayManager.class);
137 
138         // Make a new controller for the new user.
139         mListener = mNightDisplayListenerBuilder.setUser(newUserId).build();
140         if (mIsListening) {
141             mListener.setCallback(this);
142         }
143 
144         super.handleUserSwitch(newUserId);
145     }
146 
147     @Override
handleUpdateState(BooleanState state, Object arg)148     protected void handleUpdateState(BooleanState state, Object arg) {
149         state.value = mManager.isNightDisplayActivated();
150         state.label = mContext.getString(R.string.quick_settings_night_display_label);
151         state.expandedAccessibilityClassName = Switch.class.getName();
152         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
153         state.icon = ResourceIcon.get(state.value ? R.drawable.qs_nightlight_icon_on
154                 : R.drawable.qs_nightlight_icon_off);
155         state.secondaryLabel = getSecondaryLabel(state.value);
156         state.contentDescription = TextUtils.isEmpty(state.secondaryLabel)
157                 ? state.label
158                 : TextUtils.concat(state.label, ", ", state.secondaryLabel);
159     }
160 
161     /**
162      * Returns a String for the secondary label that reflects when the light will be turned on or
163      * off based on the current auto mode and night light activated status.
164      */
165     @Nullable
getSecondaryLabel(boolean isNightLightActivated)166     private String getSecondaryLabel(boolean isNightLightActivated) {
167         switch (mManager.getNightDisplayAutoMode()) {
168             case ColorDisplayManager.AUTO_MODE_TWILIGHT:
169                 if (!mLocationController.isLocationEnabled()) return null;
170                 // Auto mode related to sunrise & sunset. If the light is on, it's guaranteed to be
171                 // turned off at sunrise. If it's off, it's guaranteed to be turned on at sunset.
172                 return isNightLightActivated
173                         ? mContext.getString(
174                                 R.string.quick_settings_night_secondary_label_until_sunrise)
175                         : mContext.getString(
176                                 R.string.quick_settings_night_secondary_label_on_at_sunset);
177 
178             case ColorDisplayManager.AUTO_MODE_CUSTOM_TIME:
179                 // User-specified time, approximated to the nearest hour.
180                 final @StringRes int toggleTimeStringRes;
181                 final LocalTime toggleTime;
182                 final DateTimeFormatter toggleTimeFormat;
183 
184                 if (isNightLightActivated) {
185                     toggleTime = mManager.getNightDisplayCustomEndTime();
186                     toggleTimeStringRes = R.string.quick_settings_secondary_label_until;
187                 } else {
188                     toggleTime = mManager.getNightDisplayCustomStartTime();
189                     toggleTimeStringRes = R.string.quick_settings_night_secondary_label_on_at;
190                 }
191 
192                 // TODO(b/111085930): Move this calendar snippet to a common code location that
193                 // settings lib can also access.
194                 final Calendar c = Calendar.getInstance();
195                 DateFormat nightTileFormat = android.text.format.DateFormat.getTimeFormat(mContext);
196                 nightTileFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
197                 c.setTimeZone(nightTileFormat.getTimeZone());
198                 c.set(Calendar.HOUR_OF_DAY, toggleTime.getHour());
199                 c.set(Calendar.MINUTE, toggleTime.getMinute());
200                 c.set(Calendar.SECOND, 0);
201                 c.set(Calendar.MILLISECOND, 0);
202                 return mContext.getString(toggleTimeStringRes, nightTileFormat.format(c.getTime()));
203 
204             default:
205                 // No secondary label when auto mode is disabled.
206                 return null;
207         }
208     }
209 
210     @Override
getMetricsCategory()211     public int getMetricsCategory() {
212         return MetricsEvent.QS_NIGHT_DISPLAY;
213     }
214 
215     @Override
populate(LogMaker logMaker)216     public LogMaker populate(LogMaker logMaker) {
217         return super.populate(logMaker)
218                 .addTaggedData(FIELD_QS_MODE, mManager.getNightDisplayAutoModeRaw());
219     }
220 
221     @Override
getLongClickIntent()222     public Intent getLongClickIntent() {
223         return new Intent(Settings.ACTION_NIGHT_DISPLAY_SETTINGS);
224     }
225 
226     @Override
handleSetListening(boolean listening)227     protected void handleSetListening(boolean listening) {
228         super.handleSetListening(listening);
229         mIsListening = listening;
230         if (listening) {
231             mListener.setCallback(this);
232             refreshState();
233         } else {
234             mListener.setCallback(null);
235         }
236     }
237 
238     @Override
getTileLabel()239     public CharSequence getTileLabel() {
240         return mContext.getString(R.string.quick_settings_night_display_label);
241     }
242 
243     @Override
onActivated(boolean activated)244     public void onActivated(boolean activated) {
245         refreshState();
246     }
247 }
248