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