Add an option to display HVAC temp values in sysui

Bug: 77148007
Test: on device that has a real HVAC unit
Change-Id: I97b303dd947858157ede72c5d537ae6a1e40cc67
This commit is contained in:
Brad Stenning
2018-03-27 13:57:29 -07:00
parent ff23ffa8ff
commit 38b46f82f7
18 changed files with 611 additions and 69 deletions

View File

@@ -56,7 +56,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
SystemUI-tags \
SystemUI-proto
LOCAL_JAVA_LIBRARIES := telephony-common
LOCAL_JAVA_LIBRARIES := telephony-common \
android.car
LOCAL_PACKAGE_NAME := SystemUI
LOCAL_PRIVATE_PLATFORM_APIS := true

View File

@@ -205,6 +205,9 @@
<!-- Listen app op changes -->
<uses-permission android:name="android.permission.WATCH_APPOPS" />
<!-- to read and change hvac values in a car -->
<uses-permission android:name="android.car.permission.ADJUST_CAR_CLIMATE" />
<application
android:name=".SystemUIApplication"
android:persistent="true"

View File

@@ -42,6 +42,7 @@
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:animateLayoutChanges="true"
android:src="@drawable/car_ic_arrow"
android:background="@android:color/transparent"
android:scaleType="fitCenter">
</com.android.keyguard.AlphaOptimizedImageButton>

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
**
** Copyright 2018, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<com.android.systemui.statusbar.car.CarNavigationBarView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="vertical"
android:background="@drawable/system_bar_background">
<LinearLayout
android:layout_height="match_parent"
android:layout_width="match_parent"
android:id="@+id/nav_buttons"
android:orientation="vertical"
android:gravity="top"
android:paddingTop="30dp"
android:layout_weight="1"
android:background="@drawable/system_bar_background"
android:animateLayoutChanges="true">
<com.android.systemui.statusbar.car.CarNavigationButton
android:id="@+id/home"
android:layout_height="wrap_content"
android:layout_width="match_parent"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;end"
android:src="@drawable/car_ic_overview"
android:background="?android:attr/selectableItemBackground"
android:paddingTop="30dp"
android:paddingBottom="30dp"
/>
<com.android.systemui.statusbar.car.CarNavigationButton
android:id="@+id/hvac"
android:layout_height="wrap_content"
android:layout_width="match_parent"
systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
systemui:broadcast="true"
android:src="@drawable/car_ic_hvac"
android:background="?android:attr/selectableItemBackground"
android:paddingTop="30dp"
android:paddingBottom="30dp"
/>
</LinearLayout>
</com.android.systemui.statusbar.car.CarNavigationBarView>

View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
**
** Copyright 2018, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<com.android.systemui.statusbar.car.CarNavigationBarView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:background="@drawable/system_bar_background">
<LinearLayout
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:orientation="horizontal"
android:id="@+id/nav_buttons"
android:gravity="left"
android:paddingLeft="30dp"
android:layout_weight="1"
android:animateLayoutChanges="true">
<com.android.systemui.statusbar.car.CarNavigationButton
android:id="@+id/home"
android:layout_height="match_parent"
android:layout_width="wrap_content"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;end"
android:src="@drawable/car_ic_overview"
android:background="?android:attr/selectableItemBackground"
android:paddingLeft="30dp"
android:paddingRight="30dp"
/>
<com.android.systemui.statusbar.car.CarNavigationButton
android:id="@+id/hvac"
android:layout_height="match_parent"
android:layout_width="wrap_content"
systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
systemui:broadcast="true"
android:src="@drawable/car_ic_hvac"
android:background="?android:attr/selectableItemBackground"
android:paddingLeft="30dp"
android:paddingRight="30dp"
/>
</LinearLayout>
</com.android.systemui.statusbar.car.CarNavigationBarView>

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
**
** Copyright 2018, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<com.android.systemui.statusbar.car.CarNavigationBarView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="vertical"
android:background="@drawable/system_bar_background">
<LinearLayout
android:layout_height="match_parent"
android:layout_width="match_parent"
android:id="@+id/nav_buttons"
android:orientation="vertical"
android:gravity="top"
android:paddingTop="30dp"
android:layout_weight="1"
android:background="@drawable/system_bar_background"
android:animateLayoutChanges="true">
<com.android.systemui.statusbar.car.CarNavigationButton
android:id="@+id/home"
android:layout_height="wrap_content"
android:layout_width="match_parent"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;end"
android:src="@drawable/car_ic_overview"
android:background="?android:attr/selectableItemBackground"
android:paddingTop="30dp"
android:paddingBottom="30dp"
/>
<com.android.systemui.statusbar.car.CarNavigationButton
android:id="@+id/hvac"
android:layout_height="wrap_content"
android:layout_width="match_parent"
systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
systemui:broadcast="true"
android:src="@drawable/car_ic_hvac"
android:background="?android:attr/selectableItemBackground"
android:paddingTop="30dp"
android:paddingBottom="30dp"
/>
</LinearLayout>
</com.android.systemui.statusbar.car.CarNavigationBarView>

View File

@@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Extends RelativeLayout -->
<!-- Extends LinearLayout -->
<com.android.systemui.qs.car.CarStatusBarHeader
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
@@ -23,22 +23,21 @@
android:paddingStart="8dp"
android:paddingEnd="8dp" >
<include
layout="@layout/system_icons"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:layout_centerVertical="true" />
<include layout="@layout/system_icons"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical|end"
android:layout_weight="1"
/>
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:singleLine="true"
android:paddingStart="@dimen/status_bar_clock_starting_padding"
android:paddingEnd="@dimen/status_bar_clock_end_padding"
systemui:showDark="false" />
android:gravity="center_vertical|end"
/>
</com.android.systemui.qs.car.CarStatusBarHeader>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/**
** Copyright 2018, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<com.android.systemui.statusbar.car.CarNavigationBarView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:background="@drawable/system_bar_background">
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:singleLine="true"
android:paddingStart="@dimen/status_bar_clock_starting_padding"
android:paddingEnd="@dimen/status_bar_clock_end_padding"
android:gravity="center_vertical"
/>
</com.android.systemui.statusbar.car.CarNavigationBarView>

View File

@@ -27,6 +27,13 @@
<attr name="categories" format="string"/>
<!-- package names that will be added as extras to the fired intents -->
<attr name="packages" format="string" />
<!-- Alpha value to used when in selected state. Defaults 1f -->
<attr name="selectedAlpha" format="float" />
<!-- Alpha value to used when in un-selected state. Defaults 0.7f -->
<attr name="unselectedAlpha" format="float" />
<!-- Render a "more" icon. Defaults true -->
<attr name="useMoreIcon" format="boolean" />
</declare-styleable>
@@ -39,4 +46,11 @@
<!-- start the intent as a broad cast instead of an activity if true-->
<attr name="broadcast" format="boolean"/>
</declare-styleable>
<!-- Custom attributes to configure hvac values -->
<declare-styleable name="TemperatureView">
<attr name="hvacAreaId" format="integer"/>
<attr name="hvacPropertyId" format="integer"/>
<attr name="hvacTempFormat" format="string"/>
</declare-styleable>
</resources>

View File

@@ -21,6 +21,8 @@ import android.util.ArrayMap;
import com.android.systemui.Dependency.DependencyProvider;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.statusbar.NotificationEntryManager;
import com.android.systemui.statusbar.car.CarFacetButtonController;
import com.android.systemui.statusbar.car.hvac.HvacController;
/**
* Class factory to provide car specific SystemUI components.
@@ -32,5 +34,7 @@ public class CarSystemUIFactory extends SystemUIFactory {
super.injectDependencies(providers, context);
providers.put(NotificationEntryManager.class,
() -> new CarNotificationEntryManager(context));
providers.put(CarFacetButtonController.class, () -> new CarFacetButtonController(context));
providers.put(HvacController.class, () -> new HvacController(context));
}
}

View File

@@ -19,7 +19,7 @@ import android.graphics.Rect;
import android.support.annotation.IdRes;
import android.util.AttributeSet;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.LinearLayout;
import com.android.settingslib.Utils;
import com.android.systemui.BatteryMeterView;
@@ -30,7 +30,7 @@ import com.android.systemui.statusbar.policy.DarkIconDispatcher;
* A view that forms the header of the notification panel. This view will ensure that any
* status icons that are displayed are tinted accordingly to the current theme.
*/
public class CarStatusBarHeader extends RelativeLayout {
public class CarStatusBarHeader extends LinearLayout {
public CarStatusBarHeader(Context context, AttributeSet attrs) {
super(context, attrs);
}

View File

@@ -3,6 +3,7 @@ package com.android.systemui.statusbar.car;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
@@ -10,6 +11,7 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import com.android.keyguard.AlphaOptimizedImageButton;
import com.android.systemui.Dependency;
import com.android.systemui.R;
/**
@@ -21,9 +23,6 @@ import com.android.systemui.R;
* other music apps installed.
*/
public class CarFacetButton extends LinearLayout {
private static final float SELECTED_ALPHA = 1f;
private static final float UNSELECTED_ALPHA = 0.7f;
private static final String FACET_FILTER_DELIMITER = ";";
/**
* Extra information to be sent to a helper to make the decision of what app to launch when
@@ -42,6 +41,10 @@ public class CarFacetButton extends LinearLayout {
private String[] mFacetCategories;
/** App packages that are allowed to be used with this widget */
private String[] mFacetPackages;
private int mIconResourceId;
private boolean mUseMoreIcon = true;
private float mSelectedAlpha = 1f;
private float mUnselectedAlpha = 1f;
public CarFacetButton(Context context, AttributeSet attrs) {
@@ -53,6 +56,10 @@ public class CarFacetButton extends LinearLayout {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CarFacetButton);
setupIntents(typedArray);
setupIcons(typedArray);
CarFacetButtonController carFacetButtonController = Dependency.get(
CarFacetButtonController.class);
carFacetButtonController.addFacetButton(this);
}
/**
@@ -96,21 +103,25 @@ public class CarFacetButton extends LinearLayout {
private void setupIcons(TypedArray styledAttributes) {
mSelectedAlpha = styledAttributes.getFloat(
R.styleable.CarFacetButton_selectedAlpha, mSelectedAlpha);
mUnselectedAlpha = styledAttributes.getFloat(
R.styleable.CarFacetButton_unselectedAlpha, mUnselectedAlpha);
mIcon = findViewById(R.id.car_nav_button_icon);
mIcon.setScaleType(ImageView.ScaleType.CENTER);
mIcon.setClickable(false);
mIcon.setAlpha(UNSELECTED_ALPHA);
int iconResourceId = styledAttributes.getResourceId(R.styleable.CarFacetButton_icon, 0);
if (iconResourceId == 0) {
mIcon.setAlpha(mUnselectedAlpha);
mIconResourceId = styledAttributes.getResourceId(R.styleable.CarFacetButton_icon, 0);
if (mIconResourceId == 0) {
throw new RuntimeException("specified icon resource was not found and is required");
}
mIcon.setImageResource(iconResourceId);
mIcon.setImageResource(mIconResourceId);
mMoreIcon = findViewById(R.id.car_nav_button_more_icon);
mMoreIcon.setClickable(false);
mMoreIcon.setImageDrawable(getContext().getDrawable(R.drawable.car_ic_arrow));
mMoreIcon.setAlpha(UNSELECTED_ALPHA);
mMoreIcon.setAlpha(mSelectedAlpha);
mMoreIcon.setVisibility(GONE);
mUseMoreIcon = styledAttributes.getBoolean(R.styleable.CarFacetButton_useMoreIcon, true);
}
/**
@@ -145,17 +156,27 @@ public class CarFacetButton extends LinearLayout {
/**
* Updates the visual state to let the user know if it's been selected.
* @param selected true if should update the alpha of the icon to selected, false otherwise
* @param showMoreIcon true if the "more icon" should be shown, false otherwise
* @param showMoreIcon true if the "more icon" should be shown, false otherwise. Note this
* is ignored if the attribute useMoreIcon is set to false
*/
public void setSelected(boolean selected, boolean showMoreIcon) {
mSelected = selected;
if (selected) {
mMoreIcon.setVisibility(showMoreIcon ? VISIBLE : GONE);
mMoreIcon.setAlpha(SELECTED_ALPHA);
mIcon.setAlpha(SELECTED_ALPHA);
if (mUseMoreIcon) {
mMoreIcon.setVisibility(showMoreIcon ? VISIBLE : GONE);
}
mIcon.setAlpha(mSelectedAlpha);
} else {
mMoreIcon.setVisibility(GONE);
mIcon.setAlpha(UNSELECTED_ALPHA);
mIcon.setAlpha(mUnselectedAlpha);
}
}
public void setIcon(Drawable d) {
if (d != null) {
mIcon.setImageDrawable(d);
} else {
mIcon.setImageResource(mIconResourceId);
}
}
}

View File

@@ -5,8 +5,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.view.View;
import android.view.ViewGroup;
import java.util.HashMap;
import java.util.List;
@@ -29,39 +27,25 @@ public class CarFacetButtonController {
}
/**
* Goes through the supplied CarNavigationBarView and keeps track of all the CarFacetButtons
* such that it can select and unselect them based on running task chages
* @param bar that may contain CarFacetButtons
* Add facet button to this controller. The expected use is for the facet button
* to get a reference to this controller via {@link com.android.systemui.Dependency}
* and self add.
* @param facetButton
*/
public void addCarNavigationBar(CarNavigationBarView bar) {
findFacets(bar);
}
public void addFacetButton(CarFacetButton facetButton) {
String[] categories = facetButton.getCategories();
for (int j = 0; j < categories.length; j++) {
String category = categories[j];
mButtonsByCategory.put(category, facetButton);
}
private void findFacets(ViewGroup root) {
final int childCount = root.getChildCount();
for (int i = 0; i < childCount; ++i) {
final View v = root.getChildAt(i);
if (v instanceof CarFacetButton) {
CarFacetButton facetButton = (CarFacetButton) v;
String[] categories = facetButton.getCategories();
for (int j = 0; j < categories.length; j++) {
String category = categories[j];
mButtonsByCategory.put(category, facetButton);
}
String[] facetPackages = facetButton.getFacetPackages();
for (int j = 0; j < facetPackages.length; j++) {
String facetPackage = facetPackages[j];
mButtonsByPackage.put(facetPackage, facetButton);
}
} else if (v instanceof ViewGroup) {
findFacets((ViewGroup) v);
}
String[] facetPackages = facetButton.getFacetPackages();
for (int j = 0; j < facetPackages.length; j++) {
String facetPackage = facetPackages[j];
mButtonsByPackage.put(facetPackage, facetButton);
}
}
/**
* This will unselect the currently selected CarFacetButton and determine which one should be
* selected next. It does this by reading the properties on the CarFacetButton and seeing if

View File

@@ -22,6 +22,7 @@ import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.keyguard.AlphaOptimizedImageButton;
import com.android.systemui.R;
@@ -36,9 +37,11 @@ class CarNavigationBarView extends LinearLayout {
private LinearLayout mNavButtons;
private AlphaOptimizedImageButton mNotificationsButton;
private CarStatusBar mCarStatusBar;
private Context mContext;
public CarNavigationBarView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
}
@Override
@@ -46,7 +49,9 @@ class CarNavigationBarView extends LinearLayout {
mNavButtons = findViewById(R.id.nav_buttons);
mNotificationsButton = findViewById(R.id.notifications);
mNotificationsButton.setOnClickListener(this::onNotificationsClick);
if (mNotificationsButton != null) {
mNotificationsButton.setOnClickListener(this::onNotificationsClick);
}
}
void setStatusBar(CarStatusBar carStatusBar) {

View File

@@ -17,13 +17,9 @@
package com.android.systemui.statusbar.car;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.SystemProperties;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
@@ -45,8 +41,8 @@ import com.android.systemui.recents.Recents;
import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.car.hvac.HvacController;
import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.NavigationBarView;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -60,6 +56,8 @@ import java.util.Map;
public class CarStatusBar extends StatusBar implements
CarBatteryController.BatteryViewHandler {
private static final String TAG = "CarStatusBar";
public static final boolean ENABLE_HVAC_CONNECTION
= !SystemProperties.getBoolean("android.car.hvac.demo", true);
private TaskStackListenerImpl mTaskStackListener;
@@ -93,6 +91,11 @@ public class CarStatusBar extends StatusBar implements
createBatteryController();
mCarBatteryController.startListening();
if (ENABLE_HVAC_CONNECTION) {
Log.d(TAG, "Connecting to HVAC service");
Dependency.get(HvacController.class).connectToCarService();
}
}
@Override
@@ -164,7 +167,7 @@ public class CarStatusBar extends StatusBar implements
@Override
protected void createNavigationBar() {
mCarFacetButtonController = new CarFacetButtonController(mContext);
mCarFacetButtonController = Dependency.get(CarFacetButtonController.class);
if (mNavigationBarView != null) {
return;
}
@@ -225,7 +228,6 @@ public class CarStatusBar extends StatusBar implements
lp.windowAnimations = 0;
mCarFacetButtonController.addCarNavigationBar(mNavigationBarView);
mWindowManager.addView(mNavigationBarWindow, lp);
}
@@ -243,7 +245,6 @@ public class CarStatusBar extends StatusBar implements
throw new RuntimeException("Unable to build left nav bar due to missing layout");
}
mLeftNavigationBarView.setStatusBar(this);
mCarFacetButtonController.addCarNavigationBar(mLeftNavigationBarView);
WindowManager.LayoutParams leftlp = new WindowManager.LayoutParams(
widthForSides, LayoutParams.MATCH_PARENT,
@@ -275,7 +276,6 @@ public class CarStatusBar extends StatusBar implements
throw new RuntimeException("Unable to build right nav bar due to missing layout");
}
mRightNavigationBarView.setStatusBar(this);
mCarFacetButtonController.addCarNavigationBar(mRightNavigationBarView);
WindowManager.LayoutParams rightlp = new WindowManager.LayoutParams(
widthForSides, LayoutParams.MATCH_PARENT,

View File

@@ -0,0 +1,205 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.car.hvac;
import android.car.Car;
import android.car.CarNotConnectedException;
import android.car.hardware.CarPropertyValue;
import android.car.hardware.hvac.CarHvacManager;
import android.car.hardware.hvac.CarHvacManager.CarHvacEventCallback;
import android.content.ComponentName;
import android.content.Context;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
/**
* Manages the connection to the Car service and delegates value changes to the registered
* {@link TemperatureView}s
*/
public class HvacController {
public static final String TAG = "HvacController";
public final static int BIND_TO_HVAC_RETRY_DELAY = 5000;
private Context mContext;
private Handler mHandler;
private Car mCar;
private CarHvacManager mHvacManager;
private HashMap<HvacKey, TemperatureView> mTempComponents = new HashMap<>();
public HvacController(Context context) {
mContext = context;
}
/**
* Create connection to the Car service. Note: call backs from the Car service
* ({@link CarHvacManager}) will happen on the same thread this method was called from.
*/
public void connectToCarService() {
mHandler = new Handler();
mCar = Car.createCar(mContext, mServiceConnection, mHandler);
if (mCar != null) {
// note: this connect call handles the retries
mCar.connect();
}
}
/**
* Registers callbacks and initializes components upon connection.
*/
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
try {
service.linkToDeath(mRestart, 0);
mHvacManager = (CarHvacManager) mCar.getCarManager(Car.HVAC_SERVICE);
mHvacManager.registerCallback(mHardwareCallback);
initComponents();
} catch (Exception e) {
Log.e(TAG, "Failed to correctly connect to HVAC", e);
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
destroyHvacManager();
}
};
private void destroyHvacManager() {
if (mHvacManager != null) {
mHvacManager.unregisterCallback(mHardwareCallback);
mHvacManager = null;
}
}
/**
* If the connection to car service goes away then restart it.
*/
private final IBinder.DeathRecipient mRestart = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.d(TAG, "Death of HVAC triggering a restart");
if (mCar != null) {
mCar.disconnect();
}
destroyHvacManager();
mHandler.postDelayed(() -> mCar.connect(), BIND_TO_HVAC_RETRY_DELAY);
}
};
/**
* Add component to list and initialize it if the connection is up.
* @param temperatureView
*/
public void addHvacTextView(TemperatureView temperatureView) {
mTempComponents.put(
new HvacKey(temperatureView.getPropertyId(), temperatureView.getAreaId()),
temperatureView);
initComponent(temperatureView);
}
private void initComponents() {
Iterator<Map.Entry<HvacKey, TemperatureView>> iterator =
mTempComponents.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<HvacKey, TemperatureView> next = iterator.next();
initComponent(next.getValue());
}
}
private void initComponent(TemperatureView view) {
int id = view.getPropertyId();
int zone = view.getAreaId();
try {
if (mHvacManager == null || !mHvacManager.isPropertyAvailable(id, zone)) {
view.setTemp(Float.NaN);
return;
}
view.setTemp(mHvacManager.getFloatProperty(id, zone));
} catch (CarNotConnectedException e) {
view.setTemp(Float.NaN);
Log.e(TAG, "Failed to get value from hvac service", e);
}
}
/**
* Callback for getting changes from {@link CarHvacManager} and setting the UI elements to
* match.
*/
private final CarHvacEventCallback mHardwareCallback = new CarHvacEventCallback() {
@Override
public void onChangeEvent(final CarPropertyValue val) {
try {
int areaId = val.getAreaId();
int propertyId = val.getPropertyId();
TemperatureView temperatureView = mTempComponents.get(
new HvacKey(propertyId, areaId));
if (temperatureView != null) {
float value = (float) val.getValue();
temperatureView.setTemp(value);
} // else the data is not of interest
} catch (Exception e) {
// catch all so we don't take down the sysui if a new data type is
// introduced.
Log.e(TAG, "Failed handling hvac change event", e);
}
}
@Override
public void onErrorEvent(final int propertyId, final int zone) {
Log.d(TAG, "HVAC error event, propertyId: " + propertyId +
" zone: " + zone);
}
};
/**
* Key for storing {@link TemperatureView}s in a hash map
*/
private static class HvacKey {
int mPropertyId;
int mAreaId;
public HvacKey(int propertyId, int areaId) {
mPropertyId = propertyId;
mAreaId = areaId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HvacKey hvacKey = (HvacKey) o;
return mPropertyId == hvacKey.mPropertyId &&
mAreaId == hvacKey.mAreaId;
}
@Override
public int hashCode() {
return Objects.hash(mPropertyId, mAreaId);
}
}
}

View File

@@ -0,0 +1,82 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.car.hvac;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.widget.TextView;
import com.android.systemui.Dependency;
import com.android.systemui.R;
/**
* Simple text display of HVAC properties, It is designed to show temperature and is configured in
* the XML.
* XML properties:
* hvacPropertyId - Example: CarHvacManager.ID_ZONED_TEMP_SETPOINT (16385)
* hvacAreaId - Example: VehicleSeat.SEAT_ROW_1_LEFT (1)
* hvacTempFormat - Example: "%.1f\u00B0" (1 decimal and the degree symbol)
*
* Note: It registers itself with {@link HvacController}
*/
public class TemperatureView extends TextView {
private final int mAreaId;
private final int mPropertyId;
private final String mTempFormat;
public TemperatureView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TemperatureView);
mAreaId = typedArray.getInt(R.styleable.TemperatureView_hvacAreaId,-1);
mPropertyId = typedArray.getInt(R.styleable.TemperatureView_hvacPropertyId, -1);
String format = typedArray.getString(R.styleable.TemperatureView_hvacTempFormat);
mTempFormat = (format == null) ? "%.1f\u00B0" : format;
// register with controller
HvacController hvacController = Dependency.get(HvacController.class);
hvacController.addHvacTextView(this);
}
/**
* Formats the float for display
* @param temp - The current temp or NaN
*/
public void setTemp(float temp) {
if (Float.isNaN(temp)) {
setText("--");
return;
}
setText(String.format(mTempFormat, temp));
}
/**
* @return propertiyId Example: CarHvacManager.ID_ZONED_TEMP_SETPOINT (16385)
*/
public int getPropertyId() {
return mPropertyId;
}
/**
* @return hvac AreaId - Example: VehicleSeat.SEAT_ROW_1_LEFT (1)
*/
public int getAreaId() {
return mAreaId;
}
}

View File

@@ -75,7 +75,7 @@ LOCAL_JAVA_LIBRARIES := \
android.test.runner \
telephony-common \
android.test.base \
android.car
LOCAL_AAPT_FLAGS := --extra-packages com.android.systemui:com.android.keyguard