1 /* 2 * Copyright (C) 2021 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.car.hvac; 18 19 import static android.car.VehiclePropertyIds.HVAC_POWER_ON; 20 import static android.car.VehiclePropertyIds.HVAC_TEMPERATURE_SET; 21 22 import android.car.hardware.CarPropertyConfig; 23 import android.car.hardware.CarPropertyValue; 24 import android.content.Context; 25 import android.content.res.TypedArray; 26 import android.util.AttributeSet; 27 import android.view.MotionEvent; 28 import android.view.View; 29 import android.widget.LinearLayout; 30 import android.widget.TextView; 31 32 import androidx.annotation.Nullable; 33 import androidx.annotation.VisibleForTesting; 34 import androidx.core.content.ContextCompat; 35 36 import com.android.systemui.R; 37 38 import java.util.List; 39 40 public class TemperatureControlView extends LinearLayout implements HvacView { 41 protected static final int BUTTON_REPEAT_INTERVAL_MS = 500; 42 protected TextView mTempTextView; 43 protected View mIncreaseButton; 44 protected View mDecreaseButton; 45 46 private static final int INVALID_ID = -1; 47 /** 48 * @see android.car.VehiclePropertyIds#HVAC_TEMPERATURE_SET 49 */ 50 private static final int HVAC_TEMPERATURE_SET_CONFIG_ARRAY_SIZE = 6; 51 52 private final int mAreaId; 53 private final int mAvailableTextColor; 54 private final int mUnavailableTextColor; 55 56 private boolean mPowerOn = false; 57 private boolean mDisableViewIfPowerOff = false; 58 private boolean mTemperatureSetAvailable = false; 59 private HvacPropertySetter mHvacPropertySetter; 60 private String mTempInDisplay; 61 private float mMinTempC; 62 private float mMinTempF; 63 private float mMaxTempC; 64 private String mTemperatureFormatCelsius; 65 private String mTemperatureFormatFahrenheit; 66 67 private float mTemperatureIncrementCelsius; 68 private float mTemperatureIncrementFahrenheit; 69 private float mCurrentTempC = -1.0f; 70 private boolean mDisplayInFahrenheit = true; 71 TemperatureControlView(Context context, @Nullable AttributeSet attrs)72 public TemperatureControlView(Context context, @Nullable AttributeSet attrs) { 73 super(context, attrs); 74 TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.HvacView); 75 mAreaId = typedArray.getInt(R.styleable.HvacView_hvacAreaId, INVALID_ID); 76 mTemperatureFormatCelsius = getResources().getString( 77 R.string.hvac_temperature_format_celsius); 78 mTemperatureFormatFahrenheit = getResources().getString( 79 R.string.hvac_temperature_format_fahrenheit); 80 mTemperatureIncrementCelsius = getResources().getFloat( 81 R.fraction.celsius_temperature_increment); 82 mTemperatureIncrementFahrenheit = getResources().getFloat( 83 R.fraction.fahrenheit_temperature_increment); 84 85 mMinTempC = getResources().getFloat(R.dimen.hvac_min_value_celsius); 86 mMinTempF = getResources().getFloat(R.dimen.hvac_min_value_fahrenheit); 87 mMaxTempC = getResources().getFloat(R.dimen.hvac_max_value_celsius); 88 mAvailableTextColor = ContextCompat.getColor(getContext(), R.color.system_bar_text_color); 89 mUnavailableTextColor = ContextCompat.getColor(getContext(), 90 R.color.system_bar_text_unavailable_color); 91 } 92 93 @Override onFinishInflate()94 public void onFinishInflate() { 95 super.onFinishInflate(); 96 mTempTextView = requireViewById(R.id.hvac_temperature_text); 97 mIncreaseButton = requireViewById(R.id.hvac_increase_button); 98 mDecreaseButton = requireViewById(R.id.hvac_decrease_button); 99 initButtons(); 100 updateTemperatureView(); 101 } 102 103 @Override onHvacTemperatureUnitChanged(boolean usesFahrenheit)104 public void onHvacTemperatureUnitChanged(boolean usesFahrenheit) { 105 mDisplayInFahrenheit = usesFahrenheit; 106 updateTemperatureView(); 107 } 108 109 @Override onPropertyChanged(CarPropertyValue value)110 public void onPropertyChanged(CarPropertyValue value) { 111 if (value.getPropertyId() == HVAC_TEMPERATURE_SET) { 112 mCurrentTempC = (Float) value.getValue(); 113 mTemperatureSetAvailable = value.getStatus() == CarPropertyValue.STATUS_AVAILABLE; 114 } 115 116 if (value.getPropertyId() == HVAC_POWER_ON) { 117 mPowerOn = (Boolean) value.getValue(); 118 } 119 updateTemperatureView(); 120 } 121 122 @Override getHvacPropertyToView()123 public @HvacController.HvacProperty Integer getHvacPropertyToView() { 124 return HVAC_TEMPERATURE_SET; 125 } 126 127 @Override getAreaId()128 public @HvacController.AreaId Integer getAreaId() { 129 return mAreaId; 130 } 131 132 @Override setHvacPropertySetter(HvacPropertySetter hvacPropertySetter)133 public void setHvacPropertySetter(HvacPropertySetter hvacPropertySetter) { 134 mHvacPropertySetter = hvacPropertySetter; 135 } 136 137 @Override setDisableViewIfPowerOff(boolean disableViewIfPowerOff)138 public void setDisableViewIfPowerOff(boolean disableViewIfPowerOff) { 139 mDisableViewIfPowerOff = disableViewIfPowerOff; 140 } 141 142 @Override setConfigInfo(CarPropertyConfig<?> carPropertyConfig)143 public void setConfigInfo(CarPropertyConfig<?> carPropertyConfig) { 144 List<Integer> configArray = carPropertyConfig.getConfigArray(); 145 if (configArray.size() != HVAC_TEMPERATURE_SET_CONFIG_ARRAY_SIZE) { 146 return; 147 } 148 // Need to divide by 10 because config array values are 149 // temperature values that have been multiplied by 10. 150 mMinTempC = configArray.get(0) / 10f; 151 mMaxTempC = configArray.get(1) / 10f; 152 mTemperatureIncrementCelsius = configArray.get(2) / 10f; 153 mMinTempF = configArray.get(3) / 10f; 154 mTemperatureIncrementFahrenheit = configArray.get(5) / 10f; 155 } 156 157 @Override onLocaleListChanged()158 public void onLocaleListChanged() { 159 updateTemperatureView(); 160 } 161 162 /** 163 * Returns {@code true} if temperature should be available for change. 164 */ isTemperatureAvailableForChange()165 public boolean isTemperatureAvailableForChange() { 166 return HvacUtils.shouldAllowControl(mDisableViewIfPowerOff, mPowerOn) 167 && mTemperatureSetAvailable && mHvacPropertySetter != null; 168 } 169 170 /** 171 * Set the {@link OnClickListener} for the temperature TextView. 172 */ setTemperatureTextClickListener(OnClickListener onClickListener)173 public void setTemperatureTextClickListener(OnClickListener onClickListener) { 174 mTempTextView.setOnClickListener(onClickListener); 175 } 176 177 /** 178 * Updates the temperature view logic on the UI thread. 179 */ updateTemperatureViewUiThread()180 protected void updateTemperatureViewUiThread() { 181 mTempTextView.setText(mTempInDisplay); 182 boolean canChangeTemperature = isTemperatureAvailableForChange(); 183 mTempTextView.setTextColor(canChangeTemperature 184 ? mAvailableTextColor : mUnavailableTextColor); 185 mIncreaseButton.setVisibility(canChangeTemperature ? View.VISIBLE : View.INVISIBLE); 186 mDecreaseButton.setVisibility(canChangeTemperature ? View.VISIBLE : View.INVISIBLE); 187 } 188 getTempInDisplay()189 protected String getTempInDisplay() { 190 return mTempInDisplay; 191 } 192 getCurrentTempC()193 protected float getCurrentTempC() { 194 return mCurrentTempC; 195 } 196 197 @VisibleForTesting getTempFormatInFahrenheit()198 String getTempFormatInFahrenheit() { 199 return mTemperatureFormatFahrenheit; 200 } 201 202 @VisibleForTesting getTempFormatInCelsius()203 String getTempFormatInCelsius() { 204 return mTemperatureFormatCelsius; 205 } 206 207 @VisibleForTesting getCelsiusTemperatureIncrement()208 float getCelsiusTemperatureIncrement() { 209 return mTemperatureIncrementCelsius; 210 } 211 212 @VisibleForTesting getFahrenheitTemperatureIncrement()213 float getFahrenheitTemperatureIncrement() { 214 return mTemperatureIncrementFahrenheit; 215 } 216 initButtons()217 private void initButtons() { 218 mIncreaseButton.setOnClickListener((v) -> incrementTemperature(true)); 219 mDecreaseButton.setOnClickListener((v) -> incrementTemperature(false)); 220 221 setHoldToRepeatButton(mIncreaseButton); 222 setHoldToRepeatButton(mDecreaseButton); 223 } 224 incrementTemperature(boolean increment)225 private void incrementTemperature(boolean increment) { 226 if (!isTemperatureAvailableForChange()) { 227 return; 228 } 229 230 float newTempC = increment 231 ? mCurrentTempC + mTemperatureIncrementCelsius 232 : mCurrentTempC - mTemperatureIncrementCelsius; 233 newTempC = Math.min(newTempC, mMaxTempC); 234 newTempC = Math.max(newTempC, mMinTempC); 235 mHvacPropertySetter.setHvacProperty(HVAC_TEMPERATURE_SET, mAreaId, newTempC); 236 } 237 updateTemperatureView()238 private void updateTemperatureView() { 239 float tempToDisplayUnformatted = 240 mDisplayInFahrenheit ? celsiusToFahrenheit(mCurrentTempC) : mCurrentTempC; 241 242 mTempInDisplay = String.format( 243 mDisplayInFahrenheit ? mTemperatureFormatFahrenheit : mTemperatureFormatCelsius, 244 tempToDisplayUnformatted); 245 mContext.getMainExecutor().execute(this::updateTemperatureViewUiThread); 246 } 247 248 /** 249 * Configures the {@code button} to perform its click action repeatedly if pressed and held with 250 * {@link #BUTTON_REPEAT_INTERVAL_MS}. 251 */ setHoldToRepeatButton(View button)252 private void setHoldToRepeatButton(View button) { 253 Runnable repeatClickRunnable = new Runnable() { 254 @Override 255 public void run() { 256 button.performClick(); 257 mContext.getMainThreadHandler().postDelayed(this, BUTTON_REPEAT_INTERVAL_MS); 258 } 259 }; 260 261 button.setOnTouchListener((view, event) -> { 262 int action = event.getAction(); 263 switch (action) { 264 case MotionEvent.ACTION_DOWN: 265 // Handle click action here since click listener is suppressed. 266 repeatClickRunnable.run(); 267 break; 268 case MotionEvent.ACTION_UP: 269 case MotionEvent.ACTION_CANCEL: 270 mContext.getMainThreadHandler().removeCallbacks(repeatClickRunnable); 271 } 272 273 // Return true so on click listener is not called superfluously. 274 return true; 275 }); 276 } 277 celsiusToFahrenheit(float tempC)278 private float celsiusToFahrenheit(float tempC) { 279 int numIncrements = Math.round((tempC - mMinTempC) / mTemperatureIncrementCelsius); 280 return mTemperatureIncrementFahrenheit * numIncrements + mMinTempF; 281 } 282 } 283