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