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 */ 16package com.android.car.hvac; 17 18import android.app.Service; 19import android.content.ComponentName; 20import android.content.Context; 21import android.content.Intent; 22import android.content.ServiceConnection; 23import android.content.res.Resources; 24import android.graphics.PixelFormat; 25import android.os.Handler; 26import android.os.IBinder; 27import android.util.DisplayMetrics; 28import android.util.Log; 29import android.view.Gravity; 30import android.view.LayoutInflater; 31import android.view.View; 32import android.view.ViewGroup; 33import android.view.WindowManager; 34import android.widget.LinearLayout; 35import com.android.car.hvac.controllers.HvacPanelController; 36import com.android.car.hvac.ui.TemperatureBarOverlay; 37 38/** 39 * Creates a sliding panel for HVAC controls and adds it to the window manager above SystemUI. 40 */ 41public class HvacUiService extends Service { 42 private static final String TAG = "HvacUiService"; 43 44 private WindowManager mWindowManager; 45 46 private View mPanel; 47 private View mContainer; 48 49 private int mPanelCollapsedHeight; 50 private int mPanelFullExpandedHeight; 51 private int mScreenBottom; 52 private int mScreenWidth; 53 54 private int mTemperatureSideMargin; 55 private int mTemperatureOverlayWidth; 56 private int mTemperatureOverlayHeight; 57 private int mTemperatureBarCollapsedHeight; 58 59 private HvacPanelController mHvacPanelController; 60 private HvacController mHvacController; 61 62 private ViewGroup mDriverTemperatureBarTouchOverlay; 63 private ViewGroup mPassengerTemperatureBarTouchOverlay; 64 private TemperatureBarOverlay mDriverTemperatureBar; 65 private TemperatureBarOverlay mPassengerTemperatureBar; 66 67 @Override 68 public IBinder onBind(Intent intent) { 69 throw new UnsupportedOperationException("Not yet implemented."); 70 } 71 72 @Override 73 public void onCreate() { 74 Resources res = getResources(); 75 mPanelCollapsedHeight = res.getDimensionPixelSize(R.dimen.car_hvac_panel_collapsed_height); 76 mPanelFullExpandedHeight 77 = res.getDimensionPixelSize(R.dimen.car_hvac_panel_full_expanded_height); 78 79 mTemperatureSideMargin = res.getDimensionPixelSize(R.dimen.temperature_side_margin); 80 mTemperatureOverlayWidth = res.getDimensionPixelSize(R.dimen.temperature_bar_width_expanded); 81 mTemperatureOverlayHeight 82 = res.getDimensionPixelSize(R.dimen.car_hvac_panel_full_expanded_height); 83 mTemperatureBarCollapsedHeight 84 = res.getDimensionPixelSize(R.dimen.temperature_bar_collapsed_height); 85 86 LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); 87 mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE); 88 89 DisplayMetrics metrics = new DisplayMetrics(); 90 mWindowManager.getDefaultDisplay().getRealMetrics(metrics); 91 mScreenBottom = metrics.heightPixels - getStatusBarHeight(); 92 mScreenWidth = metrics.widthPixels; 93 94 WindowManager.LayoutParams params = new WindowManager.LayoutParams( 95 WindowManager.LayoutParams.WRAP_CONTENT, 96 WindowManager.LayoutParams.WRAP_CONTENT, 97 WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY, 98 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 99 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE 100 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, 101 PixelFormat.TRANSLUCENT); 102 103 params.packageName = this.getPackageName(); 104 params.gravity = Gravity.TOP | Gravity.LEFT; 105 106 params.x = 0; 107 params.y = 0; 108 109 params.width = mScreenWidth; 110 params.height = mScreenBottom; 111 disableAnimations(params); 112 113 mContainer = inflater.inflate(R.layout.hvac_panel, null); 114 mContainer.setLayoutParams(params); 115 116 // The top padding should be calculated on the screen height and the height of the 117 // expanded hvac panel. The space defined by the padding is meant to be clickable for 118 // dismissing the hvac panel. 119 int topPadding = mScreenBottom - mPanelFullExpandedHeight; 120 mContainer.setPadding(0, topPadding, 0, 0); 121 122 mContainer.setFocusable(false); 123 mContainer.setClickable(false); 124 mContainer.setFocusableInTouchMode(false); 125 126 mPanel = mContainer.findViewById(R.id.hvac_center_panel); 127 mPanel.getLayoutParams().height = mPanelCollapsedHeight; 128 129 mWindowManager.addView(mContainer, params); 130 131 createTemperatureBars(inflater); 132 mHvacPanelController = new HvacPanelController(this /* context */, mContainer, 133 mWindowManager, mDriverTemperatureBar, mPassengerTemperatureBar, 134 mDriverTemperatureBarTouchOverlay, mPassengerTemperatureBarTouchOverlay); 135 Intent bindIntent = new Intent(this /* context */, HvacController.class); 136 if (!bindService(bindIntent, mServiceConnection, Context.BIND_AUTO_CREATE)) { 137 Log.e(TAG, "Failed to connect to HvacController."); 138 } 139 } 140 141 @Override 142 public void onDestroy() { 143 mWindowManager.removeView(mPanel); 144 if(mHvacController != null){ 145 unbindService(mServiceConnection); 146 } 147 } 148 149 private ServiceConnection mServiceConnection = new ServiceConnection() { 150 @Override 151 public void onServiceConnected(ComponentName className, IBinder service) { 152 mHvacController = ((HvacController.LocalBinder) service).getService(); 153 final Context context = HvacUiService.this; 154 155 final Runnable r = new Runnable() { 156 @Override 157 public void run() { 158 // Once the hvac controller has refreshed its values from the vehicle, 159 // bind all the values. 160 mHvacPanelController.updateHvacController(mHvacController); 161 } 162 }; 163 164 if (mHvacController != null) { 165 mHvacController.requestRefresh(r, new Handler(context.getMainLooper())); 166 } 167 } 168 169 @Override 170 public void onServiceDisconnected(ComponentName className) { 171 mHvacController = null; 172 mHvacPanelController.updateHvacController(null); 173 //TODO: b/29126575 reconnect to controller if it is restarted 174 } 175 }; 176 177 private WindowManager.LayoutParams createClickableOverlayLayoutParam() { 178 return new WindowManager.LayoutParams( 179 WindowManager.LayoutParams.WRAP_CONTENT, 180 WindowManager.LayoutParams.WRAP_CONTENT, 181 WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY, 182 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 183 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, 184 PixelFormat.TRANSLUCENT); 185 } 186 187 private TemperatureBarOverlay createTemperatureBarOverlay(LayoutInflater inflater, 188 int gravity) { 189 TemperatureBarOverlay button = (TemperatureBarOverlay) inflater 190 .inflate(R.layout.hvac_temperature_bar_overlay, null); 191 192 WindowManager.LayoutParams params = createClickableOverlayLayoutParam(); 193 params.gravity = gravity; 194 params.x = mTemperatureSideMargin; 195 params.y = mScreenBottom - mTemperatureOverlayHeight; 196 params.width = mTemperatureOverlayWidth; 197 params.height = mTemperatureOverlayHeight; 198 199 disableAnimations(params); 200 button.setLayoutParams(params); 201 mWindowManager.addView(button, params); 202 203 return button; 204 } 205 206 /** 207 * Creates a touchable overlay in the dimensions of a collapsed {@link TemperatureBarOverlay}. 208 * @return a {@link ViewGroup} that was added to the {@link WindowManager} 209 */ 210 private ViewGroup addTemperatureTouchOverlay(int gravity) { 211 WindowManager.LayoutParams params = createClickableOverlayLayoutParam(); 212 params.gravity = gravity; 213 params.x = mTemperatureSideMargin; 214 params.y = mScreenBottom - mTemperatureBarCollapsedHeight; 215 params.width = mTemperatureOverlayWidth; 216 params.height = mTemperatureBarCollapsedHeight; 217 218 ViewGroup overlay = new LinearLayout(this /* context */); 219 overlay.setLayoutParams(params); 220 mWindowManager.addView(overlay, params); 221 return overlay; 222 } 223 224 private void createTemperatureBars(LayoutInflater inflater) { 225 mDriverTemperatureBar 226 = createTemperatureBarOverlay(inflater, Gravity.TOP | Gravity.LEFT); 227 mPassengerTemperatureBar 228 = createTemperatureBarOverlay(inflater, Gravity.TOP | Gravity.RIGHT); 229 230 // Create a transparent overlay that is the size of the collapsed temperature bar. 231 // It will receive touch events and trigger the expand/collapse of the panel. This is 232 // necessary since changing the height of the temperature bar overlay dynamically, causes 233 // a jank when WindowManager updates the view with a new height. This hack allows us 234 // to maintain the temperature bar overlay at constant (expanded) height and just 235 // update whether or not it is touchable/clickable. 236 mDriverTemperatureBarTouchOverlay 237 = addTemperatureTouchOverlay(Gravity.TOP | Gravity.LEFT); 238 mPassengerTemperatureBarTouchOverlay 239 = addTemperatureTouchOverlay(Gravity.TOP | Gravity.RIGHT); 240 } 241 242 /** 243 * Disables animations when window manager updates a child view. 244 */ 245 private void disableAnimations(WindowManager.LayoutParams params) { 246 try { 247 int currentFlags = (Integer) params.getClass().getField("privateFlags").get(params); 248 params.getClass().getField("privateFlags").set(params, currentFlags | 0x00000040); 249 } catch (Exception e) { 250 Log.e(TAG, "Error disabling animation"); 251 } 252 } 253 254 private int getStatusBarHeight() { 255 int result = 0; 256 int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); 257 if (resourceId > 0) { 258 result = getResources().getDimensionPixelSize(resourceId); 259 } 260 return result; 261 } 262} 263