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