• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 package com.android.car.hvac;
17 
18 import android.app.Service;
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.ServiceConnection;
23 import android.content.res.Resources;
24 import android.graphics.PixelFormat;
25 import android.os.Handler;
26 import android.os.IBinder;
27 import android.util.DisplayMetrics;
28 import android.util.Log;
29 import android.view.Gravity;
30 import android.view.LayoutInflater;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.view.WindowManager;
34 import android.widget.LinearLayout;
35 import com.android.car.hvac.controllers.HvacPanelController;
36 import 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  */
41 public 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
onBind(Intent intent)68     public IBinder onBind(Intent intent) {
69         throw new UnsupportedOperationException("Not yet implemented.");
70     }
71 
72     @Override
onCreate()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
onDestroy()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 
createClickableOverlayLayoutParam()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 
createTemperatureBarOverlay(LayoutInflater inflater, int gravity)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      */
addTemperatureTouchOverlay(int gravity)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 
createTemperatureBars(LayoutInflater inflater)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      */
disableAnimations(WindowManager.LayoutParams params)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 
getStatusBarHeight()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