Merge changes from topic "aae_navbutton_refactor"

* changes:
  Move attrs_car from SysUI to attrs in CarSystemUI
  Merge CarFacetButton and CarNavigationButton into a single class and add test coverage
This commit is contained in:
TreeHugger Robot
2020-01-07 17:54:22 +00:00
committed by Android (Google) Code Review
18 changed files with 995 additions and 630 deletions

View File

@@ -1,51 +0,0 @@
<?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.
*/
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/car_facet_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:animateLayoutChanges="true"
android:orientation="vertical">
<com.android.keyguard.AlphaOptimizedImageButton
android:id="@+id/car_nav_button_icon"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:animateLayoutChanges="true"
android:background="@android:color/transparent"
android:scaleType="fitCenter">
</com.android.keyguard.AlphaOptimizedImageButton>
<com.android.keyguard.AlphaOptimizedImageButton
android:id="@+id/car_nav_button_more_icon"
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>
</LinearLayout>
</merge>

View File

@@ -33,14 +33,14 @@
android:paddingEnd="20dp"
android:gravity="center">
<com.android.systemui.navigationbar.car.CarFacetButton
<com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/home"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
systemui:icon="@drawable/car_ic_overview"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
systemui:selectedIcon="@drawable/car_ic_overview_selected"
systemui:useMoreIcon="false"
systemui:highlightWhenSelected="true"
/>
<Space
@@ -48,14 +48,14 @@
android:layout_height="match_parent"
android:layout_weight="1"/>
<com.android.systemui.navigationbar.car.CarFacetButton
<com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/maps_nav"
style="@style/NavigationBarButton"
systemui:categories="android.intent.category.APP_MAPS"
systemui:icon="@drawable/car_ic_navigation"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MAPS;launchFlags=0x14000000;end"
systemui:selectedIcon="@drawable/car_ic_navigation_selected"
systemui:useMoreIcon="false"
systemui:highlightWhenSelected="true"
/>
<Space
@@ -63,7 +63,7 @@
android:layout_height="match_parent"
android:layout_weight="1"/>
<com.android.systemui.navigationbar.car.CarFacetButton
<com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/music_nav"
style="@style/NavigationBarButton"
systemui:categories="android.intent.category.APP_MUSIC"
@@ -71,7 +71,7 @@
systemui:intent="intent:#Intent;action=android.car.intent.action.MEDIA_TEMPLATE;launchFlags=0x10000000;end"
systemui:packages="com.android.car.media"
systemui:selectedIcon="@drawable/car_ic_music_selected"
systemui:useMoreIcon="false"
systemui:highlightWhenSelected="true"
/>
<Space
@@ -79,14 +79,14 @@
android:layout_height="match_parent"
android:layout_weight="1"/>
<com.android.systemui.navigationbar.car.CarFacetButton
<com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/phone_nav"
style="@style/NavigationBarButton"
systemui:icon="@drawable/car_ic_phone"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;package=com.android.car.dialer;launchFlags=0x10000000;end"
systemui:packages="com.android.car.dialer"
systemui:selectedIcon="@drawable/car_ic_phone_selected"
systemui:useMoreIcon="false"
systemui:highlightWhenSelected="true"
/>
<Space
@@ -94,14 +94,14 @@
android:layout_height="match_parent"
android:layout_weight="1"/>
<com.android.systemui.navigationbar.car.CarFacetButton
<com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/grid_nav"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.AppGridActivity"
systemui:icon="@drawable/car_ic_apps"
systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end"
systemui:selectedIcon="@drawable/car_ic_apps_selected"
systemui:useMoreIcon="false"
systemui:highlightWhenSelected="true"
/>
<Space
@@ -115,7 +115,6 @@
systemui:icon="@drawable/car_ic_notification"
systemui:longIntent="intent:#Intent;component=com.android.car.bugreport/.BugReportActivity;end"
systemui:selectedIcon="@drawable/car_ic_notification_selected"
systemui:useMoreIcon="false"
/>
<Space
@@ -127,7 +126,6 @@
android:id="@+id/assist"
style="@style/NavigationBarButton"
systemui:icon="@drawable/ic_mic_white"
systemui:useMoreIcon="false"
/>
</LinearLayout>

View File

@@ -31,7 +31,7 @@
android:paddingStart="@*android:dimen/car_padding_5"
android:paddingEnd="@*android:dimen/car_padding_5">
<com.android.systemui.navigationbar.car.CarFacetButton
<com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/home"
android:layout_width="@*android:dimen/car_touch_target_size"
android:layout_height="match_parent"
@@ -39,7 +39,8 @@
systemui:icon="@drawable/car_ic_overview"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
systemui:selectedIcon="@drawable/car_ic_overview_selected"
systemui:useMoreIcon="false"/>
systemui:highlightWhenSelected="true"
/>
</LinearLayout>
</com.android.systemui.navigationbar.car.CarNavigationBarView>

View File

@@ -18,12 +18,34 @@
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<com.android.keyguard.AlphaOptimizedImageButton
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/car_nav_button_icon"
android:layout_height="wrap_content"
android:layout_width="@dimen/car_navigation_button_width"
android:layout_centerInParent="true"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:animateLayoutChanges="true"
android:scaleType="fitCenter">
</com.android.keyguard.AlphaOptimizedImageButton>
android:orientation="vertical">
<com.android.keyguard.AlphaOptimizedImageButton
android:id="@+id/car_nav_button_icon_image"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:animateLayoutChanges="true"
android:background="@android:color/transparent"
android:scaleType="fitCenter">
</com.android.keyguard.AlphaOptimizedImageButton>
<com.android.keyguard.AlphaOptimizedImageButton
android:id="@+id/car_nav_button_more_icon"
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>
</LinearLayout>
</merge>

View File

@@ -16,6 +16,12 @@
-->
<resources>
<attr name="icon" format="reference"/>
<attr name="selectedIcon" format="reference"/>
<attr name="intent" format="string"/>
<attr name="longIntent" format="string"/>
<attr name="selectedAlpha" format="float" />
<attr name="unselectedAlpha" format="float" />
<!-- Custom attributes to configure hvac values -->
<declare-styleable name="AnimatedTemperatureView">
@@ -32,4 +38,67 @@
<attr name="android:minEms"/>
<attr name="android:textAppearance"/>
</declare-styleable>
<!-- Allow for custom attribs to be added to a nav button -->
<declare-styleable name="CarNavigationButton">
<!-- intent to start when button is click -->
<attr name="intent" />
<!-- intent to start when a long press has happened -->
<attr name="longIntent" />
<!-- start the intent as a broad cast instead of an activity if true-->
<attr name="broadcast" format="boolean"/>
<!-- Alpha value to used when in selected state. Defaults 1f -->
<attr name="selectedAlpha" />
<!-- Alpha value to used when in un-selected state. Defaults 0.7f -->
<attr name="unselectedAlpha" />
<!-- icon to be rendered when in selected state -->
<attr name="selectedIcon" />
<!-- icon to be rendered (drawable) -->
<attr name="icon"/>
<!-- categories that will be added as extras to the fired intents -->
<attr name="categories" format="string"/>
<!-- package names that will be added as extras to the fired intents -->
<attr name="packages" format="string" />
<!-- componentName names that will be used for detecting selected state -->
<attr name="componentNames" format="string" />
<!-- whether to highlight the button when selected. Defaults false -->
<attr name="showMoreWhenSelected" format="boolean" />
<!-- whether to highlight the button when selected. Defaults false -->
<attr name="highlightWhenSelected" 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>
<declare-styleable name="carVolumeItems"/>
<declare-styleable name="carVolumeItems_item">
<!-- Align with AudioAttributes.USAGE_* -->
<attr name="usage">
<enum name="unknown" value="0"/>
<enum name="media" value="1"/>
<enum name="voice_communication" value="2"/>
<enum name="voice_communication_signalling" value="3"/>
<enum name="alarm" value="4"/>
<enum name="notification" value="5"/>
<enum name="notification_ringtone" value="6"/>
<enum name="notification_communication_request" value="7"/>
<enum name="notification_communication_instant" value="8"/>
<enum name="notification_communication_delayed" value="9"/>
<enum name="notification_event" value="10"/>
<enum name="assistance_accessibility" value="11"/>
<enum name="assistance_navigation_guidance" value="12"/>
<enum name="assistance_sonification" value="13"/>
<enum name="game" value="14"/>
<!-- hidden, do not use -->
<!-- enum name="virtual_source" value="15"/ -->
<enum name="assistant" value="16"/>
</attr>
<!-- Icon resource ids to render on UI -->
<attr name="icon" />
</declare-styleable>
</resources>

View File

@@ -30,9 +30,9 @@ import com.android.internal.app.IVoiceInteractionSessionShowCallback;
/**
* AssitantButton is a ui component that will trigger the Voice Interaction Service.
*/
public class AssitantButton extends CarFacetButton {
public class AssitantButton extends CarNavigationButton {
private static final String TAG = "CarFacetButton";
private static final String TAG = "AssistantButton";
private final AssistUtils mAssistUtils;
private IVoiceInteractionSessionShowCallback mShowCallback =
new IVoiceInteractionSessionShowCallback.Stub() {
@@ -62,7 +62,7 @@ public class AssitantButton extends CarFacetButton {
}
@Override
protected void setupIntents(TypedArray typedArray) {
protected void setUpIntents(TypedArray typedArray) {
// left blank because for the assistant button Intent will not be passed from the layout.
}
}

View File

@@ -28,7 +28,6 @@ import android.view.ViewGroup;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
@@ -36,151 +35,169 @@ import javax.inject.Inject;
import javax.inject.Singleton;
/**
* CarFacetButtons placed on the nav bar are designed to have visual indication that the active
* application on screen is associated with it. This is basically a similar concept to a radio
* button group.
* CarNavigationButtons can optionally have selection state that toggles certain visual indications
* based on whether the active application on screen is associated with it. This is basically a
* similar concept to a radio button group.
*
* This class controls the selection state of CarNavigationButtons that have opted in to have such
* selection state-dependent visual indications.
*/
@Singleton
public class CarFacetButtonController {
public class ButtonSelectionStateController {
private final Set<CarFacetButton> mRegisteredViews = new HashSet<>();
private final Set<CarNavigationButton> mRegisteredViews = new HashSet<>();
protected ButtonMap mButtonsByCategory = new ButtonMap();
protected ButtonMap mButtonsByPackage = new ButtonMap();
protected ButtonMap mButtonsByComponentName = new ButtonMap();
protected HashSet<CarFacetButton> mSelectedFacetButtons;
protected HashSet<CarNavigationButton> mSelectedButtons;
protected Context mContext;
@Inject
public CarFacetButtonController(Context context) {
public ButtonSelectionStateController(Context context) {
mContext = context;
mSelectedFacetButtons = new HashSet<>();
mSelectedButtons = new HashSet<>();
}
/**
* 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.
*/
private void addFacetButton(CarFacetButton facetButton) {
if (mRegisteredViews.contains(facetButton)) {
return;
}
String[] categories = facetButton.getCategories();
for (int i = 0; i < categories.length; i++) {
mButtonsByCategory.add(categories[i], facetButton);
}
String[] facetPackages = facetButton.getFacetPackages();
for (int i = 0; i < facetPackages.length; i++) {
mButtonsByPackage.add(facetPackages[i], facetButton);
}
String[] componentNames = facetButton.getComponentName();
for (int i = 0; i < componentNames.length; i++) {
mButtonsByComponentName.add(componentNames[i], facetButton);
}
mRegisteredViews.add(facetButton);
}
/** Removes all buttons from the button maps. */
public void removeAll() {
mButtonsByCategory.clear();
mButtonsByPackage.clear();
mButtonsByComponentName.clear();
mSelectedFacetButtons.clear();
mRegisteredViews.clear();
}
/**
* Iterate through a view looking for CarFacetButtons and adding them to the controller if found
* Iterate through a view looking for CarNavigationButton and add it to the controller if it
* opted in to be highlighted when the active application is associated with it.
*
* @param v the View that may contain CarFacetButtons
*/
public void addAllFacetButtons(View v) {
if (v instanceof CarFacetButton) {
addFacetButton((CarFacetButton) v);
protected void addAllButtonsWithSelectionState(View v) {
if (v instanceof CarNavigationButton) {
if (((CarNavigationButton) v).hasSelectionState()) {
addButtonWithSelectionState((CarNavigationButton) v);
}
} else if (v instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) v;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
addAllFacetButtons(viewGroup.getChildAt(i));
addAllButtonsWithSelectionState(viewGroup.getChildAt(i));
}
}
}
/** Removes all buttons from the button maps. */
protected void removeAll() {
mButtonsByCategory.clear();
mButtonsByPackage.clear();
mButtonsByComponentName.clear();
mSelectedButtons.clear();
mRegisteredViews.clear();
}
/**
* 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
* they are a match with the supplied StackInfo list.
* This will unselect the currently selected CarNavigationButton and determine which one should
* be selected next. It does this by reading the properties on the CarNavigationButton and
* seeing if they are a match with the supplied StackInfo list.
* The order of selection detection is ComponentName, PackageName then Category
* They will then be compared with the supplied StackInfo list.
* The StackInfo is expected to be supplied in order of recency and StackInfo will only be used
* for consideration if it has the same displayId as the CarFacetButtons.
* for consideration if it has the same displayId as the CarNavigationButton.
*
* @param stackInfoList of the currently running application
* @param validDisplay index of the valid display
*/
public void taskChanged(List<ActivityManager.StackInfo> stackInfoList) {
protected void taskChanged(List<ActivityManager.StackInfo> stackInfoList, int validDisplay) {
ActivityManager.StackInfo validStackInfo = null;
for (ActivityManager.StackInfo stackInfo : stackInfoList) {
// Find the first stack info with a topActivity in the primary display.
// TODO: We assume that CarFacetButton will launch an app only in the primary display.
// We need to extend the functionality to handle the mutliple display properly.
if (stackInfo.topActivity != null && stackInfo.displayId == Display.DEFAULT_DISPLAY) {
// We need to extend the functionality to handle the multiple display properly.
if (stackInfo.topActivity != null && stackInfo.displayId == validDisplay) {
validStackInfo = stackInfo;
break;
}
}
if (validStackInfo == null) {
// No stack was found that was on the same display as the facet buttons thus return
// No stack was found that was on the same display as the buttons thus return
return;
}
int displayId = validStackInfo.displayId;
if (mSelectedFacetButtons != null) {
Iterator<CarFacetButton> iterator = mSelectedFacetButtons.iterator();
while (iterator.hasNext()) {
CarFacetButton carFacetButton = iterator.next();
if (carFacetButton.getDisplayId() == validStackInfo.displayId) {
carFacetButton.setSelected(false);
iterator.remove();
mSelectedButtons.forEach(carNavigationButton -> {
if (carNavigationButton.getDisplayId() == displayId) {
carNavigationButton.setSelected(false);
}
});
mSelectedButtons.clear();
HashSet<CarNavigationButton> selectedButtons = findSelectedButtons(validStackInfo);
if (selectedButtons != null) {
selectedButtons.forEach(carNavigationButton -> {
if (carNavigationButton.getDisplayId() == displayId) {
carNavigationButton.setSelected(true);
mSelectedButtons.add(carNavigationButton);
}
}
});
}
String packageName = validStackInfo.topActivity.getPackageName();
HashSet<CarFacetButton> facetButton =
findFacetButtonByComponentName(validStackInfo.topActivity);
if (facetButton == null) {
facetButton = mButtonsByPackage.get(packageName);
}
if (facetButton == null) {
String category = getPackageCategory(packageName);
if (category != null) {
facetButton = mButtonsByCategory.get(category);
}
}
if (facetButton != null) {
for (CarFacetButton carFacetButton : facetButton) {
if (carFacetButton.getDisplayId() == validStackInfo.displayId) {
carFacetButton.setSelected(true);
mSelectedFacetButtons.add(carFacetButton);
}
}
}
}
private HashSet<CarFacetButton> findFacetButtonByComponentName(ComponentName componentName) {
HashSet<CarFacetButton> buttons =
/**
* Defaults to Display.DEFAULT_DISPLAY when no parameter is provided for the validDisplay.
*
* @param stackInfoList
*/
protected void taskChanged(List<ActivityManager.StackInfo> stackInfoList) {
taskChanged(stackInfoList, Display.DEFAULT_DISPLAY);
}
/**
* Add navigation button to this controller if it uses selection state.
*/
private void addButtonWithSelectionState(CarNavigationButton carNavigationButton) {
if (mRegisteredViews.contains(carNavigationButton)) {
return;
}
String[] categories = carNavigationButton.getCategories();
for (int i = 0; i < categories.length; i++) {
mButtonsByCategory.add(categories[i], carNavigationButton);
}
String[] packages = carNavigationButton.getPackages();
for (int i = 0; i < packages.length; i++) {
mButtonsByPackage.add(packages[i], carNavigationButton);
}
String[] componentNames = carNavigationButton.getComponentName();
for (int i = 0; i < componentNames.length; i++) {
mButtonsByComponentName.add(componentNames[i], carNavigationButton);
}
mRegisteredViews.add(carNavigationButton);
}
private HashSet<CarNavigationButton> findSelectedButtons(
ActivityManager.StackInfo validStackInfo) {
String packageName = validStackInfo.topActivity.getPackageName();
HashSet<CarNavigationButton> selectedButtons =
findButtonsByComponentName(validStackInfo.topActivity);
if (selectedButtons == null) {
selectedButtons = mButtonsByPackage.get(packageName);
}
if (selectedButtons == null) {
String category = getPackageCategory(packageName);
if (category != null) {
selectedButtons = mButtonsByCategory.get(category);
}
}
return selectedButtons;
}
private HashSet<CarNavigationButton> findButtonsByComponentName(
ComponentName componentName) {
HashSet<CarNavigationButton> buttons =
mButtonsByComponentName.get(componentName.flattenToShortString());
return (buttons != null) ? buttons :
mButtonsByComponentName.get(componentName.flattenToString());
}
protected String getPackageCategory(String packageName) {
private String getPackageCategory(String packageName) {
PackageManager pm = mContext.getPackageManager();
Set<String> supportedCategories = mButtonsByCategory.keySet();
for (String category : supportedCategories) {
@@ -190,7 +207,7 @@ public class CarFacetButtonController {
intent.addCategory(category);
List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
if (list.size() > 0) {
// Cache this package name into facetPackageMap, so we won't have to query
// Cache this package name into ButtonsByPackage map, so we won't have to query
// all categories next time this package name shows up.
mButtonsByPackage.put(packageName, mButtonsByCategory.get(category));
return category;
@@ -200,13 +217,13 @@ public class CarFacetButtonController {
}
// simple multi-map
private static class ButtonMap extends HashMap<String, HashSet<CarFacetButton>> {
private static class ButtonMap extends HashMap<String, HashSet<CarNavigationButton>> {
public boolean add(String key, CarFacetButton value) {
public boolean add(String key, CarNavigationButton value) {
if (containsKey(key)) {
return get(key).add(value);
}
HashSet<CarFacetButton> set = new HashSet<>();
HashSet<CarNavigationButton> set = new HashSet<>();
set.add(value);
put(key, set);
return true;

View File

@@ -24,28 +24,25 @@ import com.android.systemui.shared.system.TaskStackChangeListener;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Lazy;
/**
* An implementation of TaskStackChangeListener, that listens for changes in the system
* task stack and notifies the navigation bar.
*/
@Singleton
class FacetButtonTaskStackListener extends TaskStackChangeListener {
private static final String TAG = FacetButtonTaskStackListener.class.getSimpleName();
class ButtonSelectionStateListener extends TaskStackChangeListener {
private static final String TAG = ButtonSelectionStateListener.class.getSimpleName();
private final Lazy<CarFacetButtonController> mFacetButtonControllerLazy;
private final ButtonSelectionStateController mButtonSelectionStateController;
@Inject
FacetButtonTaskStackListener(
Lazy<CarFacetButtonController> carFacetButtonControllerLazy) {
mFacetButtonControllerLazy = carFacetButtonControllerLazy;
ButtonSelectionStateListener(ButtonSelectionStateController carNavigationButtonController) {
mButtonSelectionStateController = carNavigationButtonController;
}
@Override
public void onTaskStackChanged() {
try {
mFacetButtonControllerLazy.get().taskChanged(
mButtonSelectionStateController.taskChanged(
ActivityTaskManager.getService().getAllStackInfos());
} catch (Exception e) {
Log.e(TAG, "Getting StackInfo from activity manager failed", e);
@@ -55,7 +52,7 @@ class FacetButtonTaskStackListener extends TaskStackChangeListener {
@Override
public void onTaskDisplayChanged(int taskId, int newDisplayId) {
try {
mFacetButtonControllerLazy.get().taskChanged(
mButtonSelectionStateController.taskChanged(
ActivityTaskManager.getService().getAllStackInfos());
} catch (Exception e) {
Log.e(TAG, "Getting StackInfo from activity manager failed", e);

View File

@@ -1,226 +0,0 @@
/*
* Copyright (C) 2019 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.navigationbar.car;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.view.Display;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.android.keyguard.AlphaOptimizedImageButton;
import com.android.systemui.R;
/**
* CarFacetButton is a ui component designed to be used as a shortcut for an app of a defined
* category. It can also render a indicator implying that there are more options of apps to launch
* using this component. This is done with a "More icon" currently an arrow as defined in the layout
* file. The class is to serve as an example.
*
* New activity will be launched on the same display as the button is on.
* Usage example: A button that allows a user to select a music app and indicate that there are
* other music apps installed.
*/
public class CarFacetButton extends LinearLayout {
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
* clicked.
*/
private static final String EXTRA_FACET_CATEGORIES = "categories";
private static final String EXTRA_FACET_PACKAGES = "packages";
private static final String EXTRA_FACET_ID = "filter_id";
private static final String EXTRA_FACET_LAUNCH_PICKER = "launch_picker";
private static final String TAG = "CarFacetButton";
private Context mContext;
private AlphaOptimizedImageButton mIcon;
private AlphaOptimizedImageButton mMoreIcon;
private boolean mSelected = false;
private String[] mComponentNames;
/** App categories that are to be used with this widget */
private String[] mFacetCategories;
/** App packages that are allowed to be used with this widget */
private String[] mFacetPackages;
private int mIconResourceId;
/**
* If defined in the xml this will be the icon that's rendered when the button is marked as
* selected
*/
private int mSelectedIconResourceId;
private boolean mUseMoreIcon = true;
private float mSelectedAlpha = 1f;
private float mUnselectedAlpha = 1f;
public CarFacetButton(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
View.inflate(context, R.layout.car_facet_button, this);
// extract custom attributes
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CarFacetButton);
setupIntents(typedArray);
setupIcons(typedArray);
}
/**
* Reads the custom attributes to setup click handlers for this component.
*/
protected void setupIntents(TypedArray typedArray) {
String intentString = typedArray.getString(R.styleable.CarFacetButton_intent);
String longPressIntentString = typedArray.getString(R.styleable.CarFacetButton_longIntent);
String categoryString = typedArray.getString(R.styleable.CarFacetButton_categories);
String packageString = typedArray.getString(R.styleable.CarFacetButton_packages);
String componentNameString =
typedArray.getString(R.styleable.CarFacetButton_componentNames);
try {
final Intent intent = Intent.parseUri(intentString, Intent.URI_INTENT_SCHEME);
intent.putExtra(EXTRA_FACET_ID, Integer.toString(getId()));
if (packageString != null) {
mFacetPackages = packageString.split(FACET_FILTER_DELIMITER);
intent.putExtra(EXTRA_FACET_PACKAGES, mFacetPackages);
}
if (categoryString != null) {
mFacetCategories = categoryString.split(FACET_FILTER_DELIMITER);
intent.putExtra(EXTRA_FACET_CATEGORIES, mFacetCategories);
}
if (componentNameString != null) {
mComponentNames = componentNameString.split(FACET_FILTER_DELIMITER);
}
setOnClickListener(v -> {
ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(mContext.getDisplayId());
intent.putExtra(EXTRA_FACET_LAUNCH_PICKER, mSelected);
mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT);
mContext.sendBroadcastAsUser(
new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), UserHandle.CURRENT);
});
if (longPressIntentString != null && (Build.IS_ENG || Build.IS_USERDEBUG)) {
final Intent longPressIntent = Intent.parseUri(longPressIntentString,
Intent.URI_INTENT_SCHEME);
setOnLongClickListener(v -> {
ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(mContext.getDisplayId());
mContext.startActivityAsUser(longPressIntent, options.toBundle(),
UserHandle.CURRENT);
mContext.sendBroadcastAsUser(
new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), UserHandle.CURRENT);
return true;
});
}
} catch (Exception e) {
throw new RuntimeException("Failed to attach intent", e);
}
}
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(mUnselectedAlpha);
mIconResourceId = styledAttributes.getResourceId(R.styleable.CarFacetButton_icon, 0);
mIcon.setImageResource(mIconResourceId);
mSelectedIconResourceId = styledAttributes.getResourceId(
R.styleable.CarFacetButton_selectedIcon, mIconResourceId);
mMoreIcon = findViewById(R.id.car_nav_button_more_icon);
mMoreIcon.setClickable(false);
mMoreIcon.setAlpha(mSelectedAlpha);
mMoreIcon.setVisibility(GONE);
mUseMoreIcon = styledAttributes.getBoolean(R.styleable.CarFacetButton_useMoreIcon, true);
}
/**
* @return The app categories the component represents
*/
public String[] getCategories() {
if (mFacetCategories == null) {
return new String[0];
}
return mFacetCategories;
}
/**
* @return The valid packages that should be considered.
*/
public String[] getFacetPackages() {
if (mFacetPackages == null) {
return new String[0];
}
return mFacetPackages;
}
/**
* @return The list of component names.
*/
public String[] getComponentName() {
if (mComponentNames == null) {
return new String[0];
}
return mComponentNames;
}
/**
* Updates the alpha of the icons to "selected" and shows the "More icon"
*
* @param selected true if the view must be selected, false otherwise
*/
public void setSelected(boolean selected) {
super.setSelected(selected);
setSelected(selected, selected);
}
/**
* 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. Note this
* is ignored if the attribute useMoreIcon is set to false
*/
public void setSelected(boolean selected, boolean showMoreIcon) {
mSelected = selected;
mIcon.setAlpha(mSelected ? mSelectedAlpha : mUnselectedAlpha);
mIcon.setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId);
if (mUseMoreIcon) {
mMoreIcon.setVisibility(showMoreIcon ? VISIBLE : GONE);
}
}
/**
* @return The id of the display the button is on or Display.INVALID_DISPLAY if it's not yet on
* a display.
*/
public int getDisplayId() {
Display display = getDisplay();
if (display == null) {
return Display.INVALID_DISPLAY;
}
return display.getDisplayId();
}
}

View File

@@ -57,12 +57,12 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks
private final WindowManager mWindowManager;
private final CarDeviceProvisionedController mCarDeviceProvisionedController;
private final CommandQueue mCommandQueue;
private final Lazy<FacetButtonTaskStackListener> mFacetButtonTaskStackListenerLazy;
private final ButtonSelectionStateListener mButtonSelectionStateListener;
private final Handler mMainHandler;
private final Lazy<KeyguardStateController> mKeyguardStateControllerLazy;
private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
private final Lazy<CarFacetButtonController> mCarFacetButtonControllerLazy;
private final ButtonSelectionStateController mButtonSelectionStateController;
private IStatusBarService mBarService;
private ActivityManagerWrapper mActivityManagerWrapper;
@@ -92,24 +92,24 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks
WindowManager windowManager,
DeviceProvisionedController deviceProvisionedController,
CommandQueue commandQueue,
Lazy<FacetButtonTaskStackListener> facetButtonTaskStackListenerLazy,
ButtonSelectionStateListener buttonSelectionStateListener,
@Main Handler mainHandler,
Lazy<KeyguardStateController> keyguardStateControllerLazy,
Lazy<NavigationBarController> navigationBarControllerLazy,
SuperStatusBarViewFactory superStatusBarViewFactory,
Lazy<CarFacetButtonController> carFacetButtonControllerLazy) {
ButtonSelectionStateController buttonSelectionStateController) {
super(context);
mCarNavigationBarController = carNavigationBarController;
mWindowManager = windowManager;
mCarDeviceProvisionedController = (CarDeviceProvisionedController)
deviceProvisionedController;
mCommandQueue = commandQueue;
mFacetButtonTaskStackListenerLazy = facetButtonTaskStackListenerLazy;
mButtonSelectionStateListener = buttonSelectionStateListener;
mMainHandler = mainHandler;
mKeyguardStateControllerLazy = keyguardStateControllerLazy;
mNavigationBarControllerLazy = navigationBarControllerLazy;
mSuperStatusBarViewFactory = superStatusBarViewFactory;
mCarFacetButtonControllerLazy = carFacetButtonControllerLazy;
mButtonSelectionStateController = buttonSelectionStateController;
}
@Override
@@ -156,7 +156,7 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks
createNavigationBar(result);
mActivityManagerWrapper = ActivityManagerWrapper.getInstance();
mActivityManagerWrapper.registerTaskStackListener(mFacetButtonTaskStackListenerLazy.get());
mActivityManagerWrapper.registerTaskStackListener(mButtonSelectionStateListener);
mCarNavigationBarController.connectToHvac();
}
@@ -181,7 +181,7 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks
// remove and reattach all hvac components such that we don't keep a reference to unused
// ui elements
mCarNavigationBarController.removeAllFromHvac();
mCarFacetButtonControllerLazy.get().removeAll();
mButtonSelectionStateController.removeAll();
if (mTopNavigationBarWindow != null) {
mTopNavigationBarWindow.removeAllViews();
@@ -343,7 +343,7 @@ public class CarNavigationBar extends SystemUI implements CommandQueue.Callbacks
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.print(" mTaskStackListener=");
pw.println(mFacetButtonTaskStackListenerLazy.get());
pw.println(mButtonSelectionStateListener);
pw.print(" mBottomNavigationBarView=");
pw.println(mBottomNavigationBarView);
}

View File

@@ -37,7 +37,7 @@ public class CarNavigationBarController {
private final Context mContext;
private final NavigationBarViewFactory mNavigationBarViewFactory;
private final Lazy<CarFacetButtonController> mCarFacetButtonControllerLazy;
private final ButtonSelectionStateController mButtonSelectionStateController;
private final Lazy<HvacController> mHvacControllerLazy;
private boolean mShowBottom;
@@ -58,11 +58,11 @@ public class CarNavigationBarController {
@Inject
public CarNavigationBarController(Context context,
NavigationBarViewFactory navigationBarViewFactory,
Lazy<CarFacetButtonController> carFacetButtonControllerLazy,
ButtonSelectionStateController buttonSelectionStateController,
Lazy<HvacController> hvacControllerLazy) {
mContext = context;
mNavigationBarViewFactory = navigationBarViewFactory;
mCarFacetButtonControllerLazy = carFacetButtonControllerLazy;
mButtonSelectionStateController = buttonSelectionStateController;
mHvacControllerLazy = hvacControllerLazy;
// Read configuration.
@@ -175,7 +175,7 @@ public class CarNavigationBarController {
NotificationsShadeController notifShadeController) {
view.setStatusBarWindowTouchListener(statusBarTouchListener);
view.setNotificationsPanelController(notifShadeController);
mCarFacetButtonControllerLazy.get().addAllFacetButtons(view);
mButtonSelectionStateController.addAllButtonsWithSelectionState(view);
mHvacControllerLazy.get().addTemperatureViewToController(view);
}

View File

@@ -24,8 +24,12 @@ import android.os.Build;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.android.keyguard.AlphaOptimizedImageButton;
import com.android.systemui.R;
import java.net.URISyntaxException;
@@ -35,110 +39,66 @@ import java.net.URISyntaxException;
* xml file level. This allows for more control via overlays instead of having to update
* code.
*/
public class CarNavigationButton extends com.android.keyguard.AlphaOptimizedImageButton {
private static final String TAG = "CarNavigationButton";
public class CarNavigationButton extends LinearLayout {
protected static final float DEFAULT_SELECTED_ALPHA = 1f;
protected static final float DEFAULT_UNSELECTED_ALPHA = 0.75f;
private static final String TAG = "CarNavigationButton";
private static final String BUTTON_FILTER_DELIMITER = ";";
private static final String EXTRA_BUTTON_CATEGORIES = "categories";
private static final String EXTRA_BUTTON_PACKAGES = "packages";
private static final int UNSEEN_ICON_RESOURCE_ID = R.drawable.car_ic_notification_unseen;
private static final int UNSEEN_SELECTED_ICON_RESOURCE_ID =
R.drawable.car_ic_notification_selected_unseen;
private Context mContext;
private AlphaOptimizedImageButton mIcon;
private AlphaOptimizedImageButton mMoreIcon;
private String mIntent;
private String mLongIntent;
private boolean mBroadcastIntent;
private boolean mHasUnseen = false;
private boolean mSelected = false;
private float mSelectedAlpha = 1f;
private float mUnselectedAlpha = 1f;
private float mSelectedAlpha;
private float mUnselectedAlpha;
private int mSelectedIconResourceId;
private int mIconResourceId;
private String[] mComponentNames;
/** App categories that are to be used with this widget */
private String[] mButtonCategories;
/** App packages that are allowed to be used with this widget */
private String[] mButtonPackages;
/** Whether to display more icon beneath the primary icon when the button is selected */
private boolean mShowMoreWhenSelected = false;
/** Whether to highlight the button if the active application is associated with it */
private boolean mHighlightWhenSelected = false;
public CarNavigationButton(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
View.inflate(mContext, R.layout.car_navigation_button, /* root= */ this);
// CarNavigationButton attrs
TypedArray typedArray = context.obtainStyledAttributes(
attrs, R.styleable.CarNavigationButton);
mIntent = typedArray.getString(R.styleable.CarNavigationButton_intent);
mLongIntent = typedArray.getString(R.styleable.CarNavigationButton_longIntent);
mBroadcastIntent = typedArray.getBoolean(R.styleable.CarNavigationButton_broadcast, false);
mSelectedAlpha = typedArray.getFloat(
R.styleable.CarNavigationButton_selectedAlpha, mSelectedAlpha);
mUnselectedAlpha = typedArray.getFloat(
R.styleable.CarNavigationButton_unselectedAlpha, mUnselectedAlpha);
mSelectedIconResourceId = typedArray.getResourceId(
R.styleable.CarNavigationButton_selectedIcon, mIconResourceId);
mIconResourceId = typedArray.getResourceId(
R.styleable.CarNavigationButton_icon, 0);
TypedArray typedArray = context.obtainStyledAttributes(attrs,
R.styleable.CarNavigationButton);
setUpIntents(typedArray);
setUpIcons(typedArray);
typedArray.recycle();
}
/**
* After the standard inflate this then adds the xml defined intents to click and long click
* actions if defined.
*/
@Override
public void onFinishInflate() {
super.onFinishInflate();
setScaleType(ImageView.ScaleType.CENTER);
setAlpha(mUnselectedAlpha);
setImageResource(mIconResourceId);
try {
if (mIntent != null) {
final Intent intent = Intent.parseUri(mIntent, Intent.URI_INTENT_SCHEME);
setOnClickListener(v -> {
try {
if (mBroadcastIntent) {
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
mContext.sendBroadcastAsUser(
new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
UserHandle.CURRENT);
return;
}
ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(mContext.getDisplayId());
mContext.startActivityAsUser(intent, options.toBundle(),
UserHandle.CURRENT);
} catch (Exception e) {
Log.e(TAG, "Failed to launch intent", e);
}
});
}
} catch (URISyntaxException e) {
throw new RuntimeException("Failed to attach intent", e);
}
try {
if (mLongIntent != null && (Build.IS_ENG || Build.IS_USERDEBUG)) {
final Intent intent = Intent.parseUri(mLongIntent, Intent.URI_INTENT_SCHEME);
setOnLongClickListener(v -> {
try {
ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(mContext.getDisplayId());
mContext.startActivityAsUser(intent, options.toBundle(),
UserHandle.CURRENT);
} catch (Exception e) {
Log.e(TAG, "Failed to launch intent", e);
}
// consume event either way
return true;
});
}
} catch (URISyntaxException e) {
throw new RuntimeException("Failed to attach long press intent", e);
}
}
/**
* @param selected true if should indicate if this is a selected state, false otherwise
*/
public void setSelected(boolean selected) {
super.setSelected(selected);
mSelected = selected;
setAlpha(mSelected ? mSelectedAlpha : mUnselectedAlpha);
if (mHighlightWhenSelected) {
setAlpha(mSelected ? mSelectedAlpha : mUnselectedAlpha);
}
if (mShowMoreWhenSelected && mMoreIcon != null) {
mMoreIcon.setVisibility(selected ? VISIBLE : GONE);
}
updateImage();
}
@@ -155,12 +115,165 @@ public class CarNavigationButton extends com.android.keyguard.AlphaOptimizedImag
return mHasUnseen;
}
/**
* @return The app categories the component represents
*/
public String[] getCategories() {
if (mButtonCategories == null) {
return new String[0];
}
return mButtonCategories;
}
/**
* @return The valid packages that should be considered.
*/
public String[] getPackages() {
if (mButtonPackages == null) {
return new String[0];
}
return mButtonPackages;
}
/**
* @return The list of component names.
*/
public String[] getComponentName() {
if (mComponentNames == null) {
return new String[0];
}
return mComponentNames;
}
/**
* @return The id of the display the button is on or Display.INVALID_DISPLAY if it's not yet on
* a display.
*/
protected int getDisplayId() {
Display display = getDisplay();
if (display == null) {
return Display.INVALID_DISPLAY;
}
return display.getDisplayId();
}
protected boolean hasSelectionState() {
return mHighlightWhenSelected || mShowMoreWhenSelected;
}
/**
* Sets up intents for click, long touch, and broadcast.
*/
protected void setUpIntents(TypedArray typedArray) {
mIntent = typedArray.getString(R.styleable.CarNavigationButton_intent);
mLongIntent = typedArray.getString(R.styleable.CarNavigationButton_longIntent);
mBroadcastIntent = typedArray.getBoolean(R.styleable.CarNavigationButton_broadcast, false);
String categoryString = typedArray.getString(R.styleable.CarNavigationButton_categories);
String packageString = typedArray.getString(R.styleable.CarNavigationButton_packages);
String componentNameString =
typedArray.getString(R.styleable.CarNavigationButton_componentNames);
try {
if (mIntent != null) {
final Intent intent = Intent.parseUri(mIntent, Intent.URI_INTENT_SCHEME);
setOnClickListener(v -> {
try {
if (mBroadcastIntent) {
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
mContext.sendBroadcastAsUser(
new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
UserHandle.CURRENT);
return;
}
ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(mContext.getDisplayId());
mContext.startActivityAsUser(intent, options.toBundle(),
UserHandle.CURRENT);
mContext.sendBroadcastAsUser(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
UserHandle.CURRENT);
} catch (Exception e) {
Log.e(TAG, "Failed to launch intent", e);
}
});
if (packageString != null) {
mButtonPackages = packageString.split(BUTTON_FILTER_DELIMITER);
intent.putExtra(EXTRA_BUTTON_PACKAGES, mButtonPackages);
}
if (categoryString != null) {
mButtonCategories = categoryString.split(BUTTON_FILTER_DELIMITER);
intent.putExtra(EXTRA_BUTTON_CATEGORIES, mButtonCategories);
}
if (componentNameString != null) {
mComponentNames = componentNameString.split(BUTTON_FILTER_DELIMITER);
}
}
} catch (URISyntaxException e) {
throw new RuntimeException("Failed to attach intent", e);
}
try {
if (mLongIntent != null && (Build.IS_ENG || Build.IS_USERDEBUG)) {
final Intent intent = Intent.parseUri(mLongIntent, Intent.URI_INTENT_SCHEME);
setOnLongClickListener(v -> {
try {
ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(mContext.getDisplayId());
mContext.startActivityAsUser(intent, options.toBundle(),
UserHandle.CURRENT);
mContext.sendBroadcastAsUser(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
UserHandle.CURRENT);
} catch (Exception e) {
Log.e(TAG, "Failed to launch intent", e);
}
// consume event either way
return true;
});
}
} catch (URISyntaxException e) {
throw new RuntimeException("Failed to attach long press intent", e);
}
}
/**
* Initializes view-related aspects of the button.
*/
private void setUpIcons(TypedArray typedArray) {
mSelectedAlpha = typedArray.getFloat(
R.styleable.CarNavigationButton_selectedAlpha, DEFAULT_SELECTED_ALPHA);
mUnselectedAlpha = typedArray.getFloat(
R.styleable.CarNavigationButton_unselectedAlpha, DEFAULT_UNSELECTED_ALPHA);
mHighlightWhenSelected = typedArray.getBoolean(
R.styleable.CarNavigationButton_highlightWhenSelected,
mHighlightWhenSelected);
mShowMoreWhenSelected = typedArray.getBoolean(
R.styleable.CarNavigationButton_showMoreWhenSelected,
mShowMoreWhenSelected);
mSelectedIconResourceId = typedArray.getResourceId(
R.styleable.CarNavigationButton_selectedIcon, mIconResourceId);
mIconResourceId = typedArray.getResourceId(
R.styleable.CarNavigationButton_icon, 0);
mIcon = findViewById(R.id.car_nav_button_icon_image);
mIcon.setScaleType(ImageView.ScaleType.CENTER);
mIcon.setClickable(false);
// Always apply selected alpha if the button does not toggle alpha based on selection state.
mIcon.setAlpha(mHighlightWhenSelected ? mUnselectedAlpha : mSelectedAlpha);
mIcon.setImageResource(mIconResourceId);
mMoreIcon = findViewById(R.id.car_nav_button_more_icon);
mMoreIcon.setClickable(false);
mMoreIcon.setAlpha(mSelectedAlpha);
mMoreIcon.setVisibility(GONE);
}
private void updateImage() {
if (mHasUnseen) {
setImageResource(mSelected ? UNSEEN_SELECTED_ICON_RESOURCE_ID
mIcon.setImageResource(mSelected ? UNSEEN_SELECTED_ICON_RESOURCE_ID
: UNSEEN_ICON_RESOURCE_ID);
} else {
setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId);
mIcon.setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId);
}
}
}

View File

@@ -0,0 +1,55 @@
<!--
~ Copyright (C) 2019 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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@id/nav_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:gravity="center">
<com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/detectable_by_component_name"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
systemui:icon="@drawable/car_ic_overview"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
systemui:highlightWhenSelected="true"
/>
<com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/detectable_by_category"
style="@style/NavigationBarButton"
systemui:categories="android.intent.category.APP_MAPS"
systemui:icon="@drawable/car_ic_navigation"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MAPS;launchFlags=0x14000000;end"
systemui:highlightWhenSelected="true"
/>
<com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/detectable_by_package"
style="@style/NavigationBarButton"
systemui:icon="@drawable/car_ic_phone"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;package=com.android.car.dialer;launchFlags=0x10000000;end"
systemui:packages="com.android.car.dialer"
systemui:highlightWhenSelected="true"
/>
</LinearLayout>

View File

@@ -0,0 +1,115 @@
<!--
~ Copyright (C) 2019 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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@id/nav_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:gravity="center">
<com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/default_no_selection_state"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
systemui:icon="@drawable/car_ic_overview"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
systemui:selectedIcon="@drawable/car_ic_overview_selected"
/>
<com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/app_grid_activity"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.AppGridActivity"
systemui:icon="@drawable/car_ic_apps"
systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end"
systemui:selectedIcon="@drawable/car_ic_apps_selected"
systemui:highlightWhenSelected="true"
/>
<com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/long_click_app_grid_activity"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.AppGridActivity"
systemui:icon="@drawable/car_ic_apps"
systemui:longIntent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end"
systemui:selectedIcon="@drawable/car_ic_apps_selected"
systemui:highlightWhenSelected="true"
/>
<com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/broadcast"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@null"
systemui:broadcast="true"
systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
/>
<com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/selected_icon_undefined"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
systemui:icon="@drawable/car_ic_overview"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
/>
<com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/highlightable_no_more_button"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
systemui:icon="@drawable/car_ic_overview"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
systemui:selectedIcon="@drawable/car_ic_overview_selected"
systemui:highlightWhenSelected="true"
/>
<com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/not_highlightable_more_button"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
systemui:icon="@drawable/car_ic_overview"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
systemui:selectedIcon="@drawable/car_ic_overview_selected"
systemui:showMoreWhenSelected="true"
/>
<com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/highlightable_more_button"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
systemui:icon="@drawable/car_ic_overview"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
systemui:selectedIcon="@drawable/car_ic_overview_selected"
systemui:highlightWhenSelected="true"
systemui:showMoreWhenSelected="true"
/>
<com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/broadcast"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
systemui:icon="@drawable/car_ic_overview"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
systemui:selectedIcon="@drawable/car_ic_overview_selected"
systemui:broadcast="true"
/>
</LinearLayout>

View File

@@ -0,0 +1,136 @@
/*
* Copyright (C) 2019 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.navigationbar.car;
import static com.google.common.truth.Truth.assertThat;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
import android.widget.LinearLayout;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.tests.R;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
public class ButtonSelectionStateControllerTest extends SysuiTestCase {
private static final String TEST_COMPONENT_NAME_PACKAGE = "com.android.car.carlauncher";
private static final String TEST_COMPONENT_NAME_CLASS = ".CarLauncher";
private static final String TEST_CATEGORY = "com.google.android.apps.maps";
private static final String TEST_CATEGORY_CLASS = ".APP_MAPS";
private static final String TEST_PACKAGE = "com.android.car.dialer";
private static final String TEST_PACKAGE_CLASS = ".Dialer";
// LinearLayout with CarNavigationButtons with different configurations.
private LinearLayout mTestView;
private ButtonSelectionStateController mButtonSelectionStateController;
private ComponentName mComponentName;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mTestView = (LinearLayout) LayoutInflater.from(mContext).inflate(
R.layout.car_button_selection_state_controller_test, /* root= */ null);
mButtonSelectionStateController = new ButtonSelectionStateController(mContext);
mButtonSelectionStateController.addAllButtonsWithSelectionState(mTestView);
}
@Test
public void onTaskChanged_buttonDetectableByComponentName_selectsAssociatedButton() {
CarNavigationButton testButton = mTestView.findViewById(R.id.detectable_by_component_name);
mComponentName = new ComponentName(TEST_COMPONENT_NAME_PACKAGE, TEST_COMPONENT_NAME_CLASS);
List<ActivityManager.StackInfo> testStack = createTestStack(mComponentName);
testButton.setSelected(false);
mButtonSelectionStateController.taskChanged(testStack, /* validDisplay= */ -1);
assertbuttonSelected(testButton);
}
@Test
public void onTaskChanged_buttonDetectableByCategory_selectsAssociatedButton() {
CarNavigationButton testButton = mTestView.findViewById(R.id.detectable_by_category);
mComponentName = new ComponentName(TEST_CATEGORY, TEST_CATEGORY_CLASS);
List<ActivityManager.StackInfo> testStack = createTestStack(mComponentName);
testButton.setSelected(false);
mButtonSelectionStateController.taskChanged(testStack, /* validDisplay= */ -1);
assertbuttonSelected(testButton);
}
@Test
public void onTaskChanged_buttonDetectableByPackage_selectsAssociatedButton() {
CarNavigationButton testButton = mTestView.findViewById(R.id.detectable_by_package);
mComponentName = new ComponentName(TEST_PACKAGE, TEST_PACKAGE_CLASS);
List<ActivityManager.StackInfo> testStack = createTestStack(mComponentName);
testButton.setSelected(false);
mButtonSelectionStateController.taskChanged(testStack, /* validDisplay= */ -1);
assertbuttonSelected(testButton);
}
@Test
public void onTaskChanged_deselectsPreviouslySelectedButton() {
CarNavigationButton oldButton = mTestView.findViewById(R.id.detectable_by_component_name);
mComponentName = new ComponentName(TEST_COMPONENT_NAME_PACKAGE, TEST_COMPONENT_NAME_CLASS);
List<ActivityManager.StackInfo> oldStack = createTestStack(mComponentName);
oldButton.setSelected(false);
mButtonSelectionStateController.taskChanged(oldStack, /* validDisplay= */ -1);
mComponentName = new ComponentName(TEST_PACKAGE, TEST_PACKAGE_CLASS);
List<ActivityManager.StackInfo> newStack = createTestStack(mComponentName);
mButtonSelectionStateController.taskChanged(newStack, /* validDisplay= */ -1);
assertButtonUnselected(oldButton);
}
// Comparing alpha is a valid way to verify button selection state because all test buttons use
// highlightWhenSelected = true.
private void assertbuttonSelected(CarNavigationButton button) {
assertThat(button.getAlpha()).isEqualTo(CarNavigationButton.DEFAULT_SELECTED_ALPHA);
}
private void assertButtonUnselected(CarNavigationButton button) {
assertThat(button.getAlpha()).isEqualTo(CarNavigationButton.DEFAULT_UNSELECTED_ALPHA);
}
private List<ActivityManager.StackInfo> createTestStack(ComponentName componentName) {
ActivityManager.StackInfo validStackInfo = new ActivityManager.StackInfo();
validStackInfo.displayId = -1; // No display is assigned to this test view
validStackInfo.topActivity = componentName;
List<ActivityManager.StackInfo> testStack = new ArrayList<>();
testStack.add(validStackInfo);
return testStack;
}
}

View File

@@ -51,7 +51,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
private TestableResources mTestableResources;
@Mock
private CarFacetButtonController mCarFacetButtonController;
private ButtonSelectionStateController mButtonSelectionStateController;
@Mock
private HvacController mHvacController;
@@ -69,7 +69,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
@Test
public void testConnectToHvac_callsConnect() {
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
mCarNavigationBar.connectToHvac();
@@ -79,7 +79,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
@Test
public void testRemoveAllFromHvac_callsRemoveAll() {
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
mCarNavigationBar.removeAllFromHvac();
@@ -90,7 +90,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testGetBottomWindow_bottomDisabled_returnsNull() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, false);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getBottomWindow();
@@ -101,7 +101,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testGetBottomWindow_bottomEnabled_returnsWindow() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getBottomWindow();
@@ -112,7 +112,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testGetBottomWindow_bottomEnabled_calledTwice_returnsSameWindow() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
ViewGroup window1 = mCarNavigationBar.getBottomWindow();
ViewGroup window2 = mCarNavigationBar.getBottomWindow();
@@ -124,7 +124,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testGetLeftWindow_leftDisabled_returnsNull() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, false);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getLeftWindow();
assertThat(window).isNull();
}
@@ -133,7 +133,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testGetLeftWindow_leftEnabled_returnsWindow() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getLeftWindow();
@@ -144,7 +144,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testGetLeftWindow_leftEnabled_calledTwice_returnsSameWindow() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
ViewGroup window1 = mCarNavigationBar.getLeftWindow();
ViewGroup window2 = mCarNavigationBar.getLeftWindow();
@@ -156,7 +156,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testGetRightWindow_rightDisabled_returnsNull() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, false);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getRightWindow();
@@ -167,7 +167,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testGetRightWindow_rightEnabled_returnsWindow() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getRightWindow();
@@ -178,7 +178,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testGetRightWindow_rightEnabled_calledTwice_returnsSameWindow() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
ViewGroup window1 = mCarNavigationBar.getRightWindow();
ViewGroup window2 = mCarNavigationBar.getRightWindow();
@@ -190,7 +190,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testSetBottomWindowVisibility_setTrue_isVisible() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getBottomWindow();
mCarNavigationBar.setBottomWindowVisibility(View.VISIBLE);
@@ -202,7 +202,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testSetBottomWindowVisibility_setFalse_isGone() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getBottomWindow();
mCarNavigationBar.setBottomWindowVisibility(View.GONE);
@@ -214,7 +214,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testSetLeftWindowVisibility_setTrue_isVisible() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getLeftWindow();
mCarNavigationBar.setLeftWindowVisibility(View.VISIBLE);
@@ -226,7 +226,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testSetLeftWindowVisibility_setFalse_isGone() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getLeftWindow();
mCarNavigationBar.setLeftWindowVisibility(View.GONE);
@@ -238,7 +238,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testSetRightWindowVisibility_setTrue_isVisible() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getRightWindow();
mCarNavigationBar.setRightWindowVisibility(View.VISIBLE);
@@ -250,7 +250,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testSetRightWindowVisibility_setFalse_isGone() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getRightWindow();
mCarNavigationBar.setRightWindowVisibility(View.GONE);
@@ -262,7 +262,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testRegisterBottomBarTouchListener_createViewFirst_registrationSuccessful() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View.OnTouchListener controller = bottomBar.getStatusBarWindowTouchListener();
@@ -277,7 +277,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testRegisterBottomBarTouchListener_registerFirst_registrationSuccessful() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
mCarNavigationBar.registerBottomBarTouchListener(mock(View.OnTouchListener.class));
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
@@ -290,7 +290,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testRegisterNotificationController_createViewFirst_registrationSuccessful() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
CarNavigationBarController.NotificationsShadeController controller =
@@ -307,7 +307,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testRegisterNotificationController_registerFirst_registrationSuccessful() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
mCarNavigationBar.registerNotificationController(
mock(CarNavigationBarController.NotificationsShadeController.class));
@@ -322,7 +322,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testShowAllKeyguardButtons_bottomEnabled_bottomKeyguardButtonsVisible() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View bottomKeyguardButtons = bottomBar.findViewById(R.id.lock_screen_nav_buttons);
@@ -335,7 +335,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testShowAllKeyguardButtons_bottomEnabled_bottomNavButtonsGone() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View bottomButtons = bottomBar.findViewById(R.id.nav_buttons);
@@ -348,7 +348,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testHideAllKeyguardButtons_bottomEnabled_bottomKeyguardButtonsGone() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View bottomKeyguardButtons = bottomBar.findViewById(R.id.lock_screen_nav_buttons);
@@ -363,7 +363,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testHideAllKeyguardButtons_bottomEnabled_bottomNavButtonsVisible() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View bottomButtons = bottomBar.findViewById(R.id.nav_buttons);
@@ -378,7 +378,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testToggleAllNotificationsUnseenIndicator_bottomEnabled_hasUnseen_setCorrectly() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
CarNavigationButton notifications = bottomBar.findViewById(R.id.notifications);
@@ -393,7 +393,7 @@ public class CarNavigationBarControllerTest extends SysuiTestCase {
public void testToggleAllNotificationsUnseenIndicator_bottomEnabled_noUnseen_setCorrectly() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
() -> mCarFacetButtonController, () -> mHvacController);
mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
CarNavigationButton notifications = bottomBar.findViewById(R.id.notifications);

View File

@@ -0,0 +1,222 @@
/*
* Copyright (C) 2019 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.navigationbar.car;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import androidx.test.filters.SmallTest;
import com.android.keyguard.AlphaOptimizedImageButton;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.tests.R;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
public class CarNavigationButtonTest extends SysuiTestCase {
private static final String DEFAULT_BUTTON_ACTIVITY_NAME =
"com.android.car.carlauncher/.CarLauncher";
private static final String APP_GRID_BUTTON_ACTIVITY_NAME =
"com.android.car.carlauncher/.AppGridActivity";
private static final String BROADCAST_ACTION_NAME =
"android.car.intent.action.TOGGLE_HVAC_CONTROLS";
private ActivityManager mActivityManager;
// LinearLayout with CarNavigationButtons with different configurations.
private LinearLayout mTestView;
// Does not have any selection state which is the default configuration.
private CarNavigationButton mDefaultButton;
@Before
public void setUp() {
mContext = spy(mContext);
mTestView = (LinearLayout) LayoutInflater.from(mContext).inflate(
R.layout.car_navigation_button_test, /* root= */ null);
mDefaultButton = mTestView.findViewById(R.id.default_no_selection_state);
mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
}
@Test
public void onCreate_iconIsVisible() {
AlphaOptimizedImageButton icon = mDefaultButton.findViewById(
R.id.car_nav_button_icon_image);
assertThat(icon.getDrawable()).isNotNull();
}
@Test
public void onSelected_selectedIconDefined_togglesIcon() {
mDefaultButton.setSelected(true);
Drawable selectedIconDrawable = ((AlphaOptimizedImageButton) mDefaultButton.findViewById(
R.id.car_nav_button_icon_image)).getDrawable();
mDefaultButton.setSelected(false);
Drawable unselectedIconDrawable = ((AlphaOptimizedImageButton) mDefaultButton.findViewById(
R.id.car_nav_button_icon_image)).getDrawable();
assertThat(selectedIconDrawable).isNotEqualTo(unselectedIconDrawable);
}
@Test
public void onSelected_selectedIconUndefined_displaysSameIcon() {
CarNavigationButton selectedIconUndefinedButton = mTestView.findViewById(
R.id.selected_icon_undefined);
selectedIconUndefinedButton.setSelected(true);
Drawable selectedIconDrawable = ((AlphaOptimizedImageButton) mDefaultButton.findViewById(
R.id.car_nav_button_icon_image)).getDrawable();
selectedIconUndefinedButton.setSelected(false);
Drawable unselectedIconDrawable = ((AlphaOptimizedImageButton) mDefaultButton.findViewById(
R.id.car_nav_button_icon_image)).getDrawable();
assertThat(selectedIconDrawable).isEqualTo(unselectedIconDrawable);
}
@Test
public void onUnselected_doesNotHighlightWhenSelected_applySelectedAlpha() {
mDefaultButton.setSelected(false);
assertThat(mDefaultButton.getAlpha()).isEqualTo(
CarNavigationButton.DEFAULT_SELECTED_ALPHA);
}
@Test
public void onSelected_doesNotHighlightWhenSelected_applySelectedAlpha() {
mDefaultButton.setSelected(true);
assertThat(mDefaultButton.getAlpha()).isEqualTo(
CarNavigationButton.DEFAULT_SELECTED_ALPHA);
}
@Test
public void onUnselected_highlightWhenSelected_applyDefaultUnselectedAlpha() {
CarNavigationButton highlightWhenSelectedButton = mTestView.findViewById(
R.id.highlightable_no_more_button);
highlightWhenSelectedButton.setSelected(false);
assertThat(highlightWhenSelectedButton.getAlpha()).isEqualTo(
CarNavigationButton.DEFAULT_UNSELECTED_ALPHA);
}
@Test
public void onSelected_highlightWhenSelected_applyDefaultSelectedAlpha() {
CarNavigationButton highlightWhenSelectedButton = mTestView.findViewById(
R.id.highlightable_no_more_button);
highlightWhenSelectedButton.setSelected(true);
assertThat(highlightWhenSelectedButton.getAlpha()).isEqualTo(
CarNavigationButton.DEFAULT_SELECTED_ALPHA);
}
@Test
public void onSelected_doesNotShowMoreWhenSelected_doesNotShowMoreIcon() {
mDefaultButton.setSelected(true);
AlphaOptimizedImageButton moreIcon = mDefaultButton.findViewById(
R.id.car_nav_button_more_icon);
assertThat(moreIcon.getVisibility()).isEqualTo(View.GONE);
}
@Test
public void onSelected_showMoreWhenSelected_showsMoreIcon() {
CarNavigationButton showMoreWhenSelected = mTestView.findViewById(
R.id.not_highlightable_more_button);
showMoreWhenSelected.setSelected(true);
AlphaOptimizedImageButton moreIcon = showMoreWhenSelected.findViewById(
R.id.car_nav_button_more_icon);
assertThat(moreIcon.getVisibility()).isEqualTo(View.VISIBLE);
}
@Test
public void onUnselected_showMoreWhenSelected_doesNotShowMoreIcon() {
CarNavigationButton showMoreWhenSelected = mTestView.findViewById(
R.id.highlightable_no_more_button);
showMoreWhenSelected.setSelected(true);
showMoreWhenSelected.setSelected(false);
AlphaOptimizedImageButton moreIcon = showMoreWhenSelected.findViewById(
R.id.car_nav_button_more_icon);
assertThat(moreIcon.getVisibility()).isEqualTo(View.GONE);
}
@Test
public void onClick_launchesIntentActivity() {
mDefaultButton.performClick();
assertThat(getCurrentActivityName()).isEqualTo(DEFAULT_BUTTON_ACTIVITY_NAME);
CarNavigationButton appGridButton = mTestView.findViewById(R.id.app_grid_activity);
appGridButton.performClick();
assertThat(getCurrentActivityName()).isEqualTo(APP_GRID_BUTTON_ACTIVITY_NAME);
}
@Test
public void onLongClick_longIntentDefined_launchesLongIntentActivity() {
mDefaultButton.performClick();
assertThat(getCurrentActivityName()).isEqualTo(DEFAULT_BUTTON_ACTIVITY_NAME);
CarNavigationButton appGridButton = mTestView.findViewById(
R.id.long_click_app_grid_activity);
appGridButton.performLongClick();
assertThat(getCurrentActivityName()).isEqualTo(APP_GRID_BUTTON_ACTIVITY_NAME);
}
@Test
public void onClick_useBroadcast_broadcastsIntent() {
CarNavigationButton appGridButton = mTestView.findViewById(R.id.broadcast);
appGridButton.performClick();
verify(mContext).sendBroadcastAsUser(argThat(new ArgumentMatcher<Intent>() {
@Override
public boolean matches(Intent argument) {
return argument.getAction().equals(BROADCAST_ACTION_NAME);
}
}), any());
}
private String getCurrentActivityName() {
return mActivityManager.getRunningTasks(1).get(0).topActivity.flattenToShortString();
}
}

View File

@@ -1,103 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<resources>
<attr name="icon" format="reference"/>
<attr name="selectedIcon" format="reference"/>
<attr name="intent" format="string"/>
<attr name="longIntent" format="string"/>
<attr name="selectedAlpha" format="float" />
<attr name="unselectedAlpha" format="float" />
<!-- Allow for custom attribs to be added to a facet button -->
<declare-styleable name="CarFacetButton">
<!-- icon to be rendered (drawable) -->
<attr name="icon"/>
<!-- icon to be rendered when in selected state -->
<attr name="selectedIcon"/>
<!-- intent to start when button is click -->
<attr name="intent"/>
<!-- intent to start when a long press has happened -->
<attr name="longIntent"/>
<!-- categories that will be added as extras to the fired intents -->
<attr name="categories" format="string"/>
<!-- package names that will be added as extras to the fired intents -->
<attr name="packages" format="string" />
<!-- componentName names that will be used for detecting selected state -->
<attr name="componentNames" format="string" />
<!-- Alpha value to used when in selected state. Defaults 1f -->
<attr name="selectedAlpha" />
<!-- Alpha value to used when in un-selected state. Defaults 0.7f -->
<attr name="unselectedAlpha" />
<!-- Render a "more" icon. Defaults true -->
<attr name="useMoreIcon" format="boolean" />
</declare-styleable>
<!-- Allow for custom attribs to be added to a nav button -->
<declare-styleable name="CarNavigationButton">
<!-- intent to start when button is click -->
<attr name="intent" />
<!-- intent to start when a long press has happened -->
<attr name="longIntent" />
<!-- start the intent as a broad cast instead of an activity if true-->
<attr name="broadcast" format="boolean"/>
<!-- Alpha value to used when in selected state. Defaults 1f -->
<attr name="selectedAlpha" />
<!-- Alpha value to used when in un-selected state. Defaults 0.7f -->
<attr name="unselectedAlpha" />
<!-- icon to be rendered when in selected state -->
<attr name="selectedIcon" />
<!-- icon to be rendered (drawable) -->
<attr name="icon"/>
</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>
<declare-styleable name="carVolumeItems"/>
<declare-styleable name="carVolumeItems_item">
<!-- Align with AudioAttributes.USAGE_* -->
<attr name="usage">
<enum name="unknown" value="0"/>
<enum name="media" value="1"/>
<enum name="voice_communication" value="2"/>
<enum name="voice_communication_signalling" value="3"/>
<enum name="alarm" value="4"/>
<enum name="notification" value="5"/>
<enum name="notification_ringtone" value="6"/>
<enum name="notification_communication_request" value="7"/>
<enum name="notification_communication_instant" value="8"/>
<enum name="notification_communication_delayed" value="9"/>
<enum name="notification_event" value="10"/>
<enum name="assistance_accessibility" value="11"/>
<enum name="assistance_navigation_guidance" value="12"/>
<enum name="assistance_sonification" value="13"/>
<enum name="game" value="14"/>
<!-- hidden, do not use -->
<!-- enum name="virtual_source" value="15"/ -->
<enum name="assistant" value="16"/>
</attr>
<!-- Icon resource ids to render on UI -->
<attr name="icon" />
</declare-styleable>
</resources>