Adding new Style of decor windows for Car use case.

This allows for left, right or bottom navigation bars that can be
configured with xml files

Bug: 62364016
Test: Manual

Change-Id: If0c27ce30fa126491caf715f0e949134f93cdeaf
This commit is contained in:
Brad Stenning
2017-12-18 08:25:10 -08:00
parent d83226fd8d
commit 078235b17d
16 changed files with 939 additions and 659 deletions

View File

@@ -0,0 +1,51 @@
<!--
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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="37dp"
android:height="31dp"
android:viewportWidth="37"
android:viewportHeight="31">
<group
android:translateX="-4.000000"
android:translateY="-6.000000">
<group
android:translateX="5.000000"
android:translateY="5.000000">
<path
android:fillType="evenOdd"
android:strokeColor="#FAFAFA"
android:strokeWidth="3.5"
android:pathData="M0.320769938,6.07518051 C6.46754647,1.46509811 12.4222362,1.46509811
18.1848392,6.07518051 C23.9474422,10.6852629 29.3258717,10.4931761
34.3201276,5.49892021" />
<path
android:fillType="evenOdd"
android:strokeColor="#FAFAFA"
android:strokeWidth="3.5"
android:pathData="M0.320769938,17.0751805 C6.46754647,12.4650981 12.4222362,12.4650981
18.1848392,17.0751805 C23.9474422,21.6852629 29.3258717,21.4931761
34.3201276,16.4989202" />
<path
android:fillType="evenOdd"
android:strokeColor="#FAFAFA"
android:strokeWidth="3.5"
android:pathData="M0.320769938,28.0751805 C6.46754647,23.4650981 12.4222362,23.4650981
18.1848392,28.0751805 C23.9474422,32.6852629 29.3258717,32.4931761
34.3201276,27.4989202" />
</group>
</group>
</vector>

View File

@@ -0,0 +1,28 @@
<!--
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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="#FFFFFF"
android:pathData="M24 44c2.21 0 4-1.79 4-4h-8c0 2.21 1.79 4 4
4zm12-12V22c0-6.15-3.27-11.28-9-12.64V8c0-1.66-1.34-3-3-3s-3 1.34-3 3v1.36c-5.73
1.36-9 6.49-9 12.64v10l-4 4v2h32v-2l-4-4zm-4 2H16V22c0-4.97 3.03-9 8-9s8 4.03 8
9v12z" />
</vector>

View File

@@ -0,0 +1,28 @@
<!--
Copyright (C) 2016 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="56dp"
android:height="56dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:pathData="M0 0h48v48H0z" />
<path
android:fillColor="#FFFFFFFF"
android:pathData="M24 4C12.95 4 4 12.95 4 24s8.95 20 20 20 20-8.95 20-20S35.05 4 24 4zm0 36c-8.82
0-16-7.18-16-16S15.18 8 24 8s16 7.18 16 16-7.18 16-16 16z" />
</vector>

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
**
** Copyright 2017, 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:background="@android:color/transparent"
android:scaleType="fitCenter">
</com.android.keyguard.AlphaOptimizedImageButton>
</LinearLayout>
</merge>

View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
**
** Copyright 2016, 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.SHOW_HVAC_CONTROLS;end"
systemui:broadcast="true"
android:src="@drawable/car_ic_hvac"
android:background="?android:attr/selectableItemBackground"
android:paddingTop="30dp"
android:paddingBottom="30dp"
/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="bottom"
android:orientation="vertical">
<com.android.keyguard.AlphaOptimizedImageButton
android:id="@+id/notifications"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:src="@drawable/car_ic_notification"
android:background="?android:attr/selectableItemBackground"
android:paddingTop="20dp"
android:paddingBottom="20dp"
android:alpha="0.7"
/>
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:singleLine="true"
android:paddingStart="@dimen/status_bar_clock_starting_padding"
android:paddingEnd="@dimen/status_bar_clock_end_padding"
android:gravity="center_horizontal"
android:paddingBottom="20dp"
/>
<Space
android:layout_height="10dp"
android:layout_width="match_parent"/>
</LinearLayout>
</com.android.systemui.statusbar.car.CarNavigationBarView>

View File

@@ -19,34 +19,80 @@
<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:gravity="center"
android:background="@drawable/system_bar_background">
<!-- phone.NavigationBarView has rot0 and rot90 but we expect the car head unit to have a fixed
rotation so skip this level of the heirarchy.
-->
<LinearLayout
android:layout_height="match_parent"
android:layout_width="@dimen/car_navigation_bar_width"
android:layout_width="wrap_content"
android:orientation="horizontal"
android:clipChildren="false"
android:clipToPadding="false"
android:id="@+id/nav_buttons"
android:gravity="left"
android:paddingLeft="30dp"
android:layout_weight="1"
android:animateLayoutChanges="true">
<!-- Buttons get populated here from a car_arrays.xml. -->
<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.SHOW_HVAC_CONTROLS;end"
systemui:broadcast="true"
android:src="@drawable/car_ic_hvac"
android:background="?android:attr/selectableItemBackground"
android:paddingLeft="30dp"
android:paddingRight="30dp"
/>
</LinearLayout>
<!-- lights out layout to match exactly -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="horizontal"
android:id="@+id/lights_out"
android:visibility="gone">
<!-- Must match nav_buttons. -->
android:layout_weight="1"
android:gravity="right"
android:orientation="horizontal">
<com.android.keyguard.AlphaOptimizedImageButton
android:id="@+id/notifications"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:src="@drawable/car_ic_notification"
android:background="?android:attr/selectableItemBackground"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:alpha="0.7"
/>
<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"
android:paddingRight="20dp"
/>
<Space
android:layout_width="10dp"
android:layout_height="match_parent"/>
</LinearLayout>
</com.android.systemui.statusbar.car.CarNavigationBarView>

View File

@@ -17,28 +17,13 @@
*/
-->
<com.android.systemui.statusbar.car.CarNavigationButton
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:orientation="horizontal"
android:background="?android:attr/selectableItemBackground">
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<com.android.keyguard.AlphaOptimizedImageButton
android:id="@+id/car_nav_button_icon"
android:layout_height="match_parent"
android:layout_width="@dimen/car_navigation_button_width"
android:layout_centerInParent="true"
android:animateLayoutChanges="true"
android:scaleType="fitCenter">
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:animateLayoutChanges="true"
android:scaleType="fitCenter">
</com.android.keyguard.AlphaOptimizedImageButton>
<com.android.keyguard.AlphaOptimizedImageButton
android:id="@+id/car_nav_button_more_icon"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/car_nav_button_icon"
android:animateLayoutChanges="true"
android:scaleType="fitCenter">
</com.android.keyguard.AlphaOptimizedImageButton>
</com.android.systemui.statusbar.car.CarNavigationButton>
</merge>

View File

@@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
**
** Copyright 2016, 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.SHOW_HVAC_CONTROLS;end"
systemui:broadcast="true"
android:src="@drawable/car_ic_hvac"
android:background="?android:attr/selectableItemBackground"
android:paddingTop="30dp"
android:paddingBottom="30dp"
/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="bottom"
android:orientation="vertical">
<com.android.keyguard.AlphaOptimizedImageButton
android:id="@+id/notifications"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:src="@drawable/car_ic_notification"
android:background="?android:attr/selectableItemBackground"
android:paddingTop="20dp"
android:paddingBottom="20dp"
android:alpha="0.7"
/>
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:singleLine="true"
android:paddingStart="@dimen/status_bar_clock_starting_padding"
android:paddingEnd="@dimen/status_bar_clock_end_padding"
android:gravity="center_horizontal"
android:paddingBottom="20dp"
/>
<Space
android:layout_height="10dp"
android:layout_width="match_parent"/>
</LinearLayout>
</com.android.systemui.statusbar.car.CarNavigationBarView>

View File

@@ -0,0 +1,42 @@
<?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>
<!-- Allow for custom attribs to be added to a facet button -->
<declare-styleable name="CarFacetButton">
<!-- icon to be rendered (drawable) -->
<attr name="icon" format="reference"/>
<!-- intent to start when button is click -->
<attr name="intent" format="string"/>
<!-- intent to start when a long press has happened -->
<attr name="longIntent" format="string"/>
<!-- 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" />
</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" format="string"/>
<!-- intent to start when a long press has happened -->
<attr name="longIntent" format="string"/>
<!-- start the intent as a broad cast instead of an activity if true-->
<attr name="broadcast" format="boolean"/>
</declare-styleable>
</resources>

View File

@@ -22,4 +22,9 @@
uri that will be launched into the docked window. -->
<bool name="config_enablePersistentDockedActivity">false</bool>
<string name="config_persistentDockedActivityIntentUri" translatable="false"></string>
<!-- configure which system ui bars should be displayed -->
<bool name="config_enableLeftNavigationBar">false</bool>
<bool name="config_enableRightNavigationBar">false</bool>
<bool name="config_enableBottomNavigationBar">true</bool>
</resources>

View File

@@ -0,0 +1,161 @@
package com.android.systemui.statusbar.car;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.Log;
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 impling 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.
* 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 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
* 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 Context mContext;
private AlphaOptimizedImageButton mIcon;
private AlphaOptimizedImageButton mMoreIcon;
private boolean mSelected = false;
/** 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;
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.
*/
private 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);
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);
}
setOnClickListener(v -> {
intent.putExtra(EXTRA_FACET_LAUNCH_PICKER, mSelected);
mContext.startActivity(intent);
});
if (longPressIntentString != null) {
final Intent longPressIntent = Intent.parseUri(longPressIntentString,
Intent.URI_INTENT_SCHEME);
setOnLongClickListener(v -> {
mContext.startActivity(longPressIntent);
return true;
});
}
} catch (Exception e) {
throw new RuntimeException("Failed to attach intent", e);
}
}
private void setupIcons(TypedArray styledAttributes) {
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) {
throw new RuntimeException("specified icon resource was not found and is required");
}
mIcon.setImageResource(iconResourceId);
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.setVisibility(GONE);
}
/**
* @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;
}
/**
* 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
*/
public void setSelected(boolean selected, boolean showMoreIcon) {
mSelected = selected;
if (selected) {
mMoreIcon.setVisibility(showMoreIcon ? VISIBLE : GONE);
mMoreIcon.setAlpha(SELECTED_ALPHA);
mIcon.setAlpha(SELECTED_ALPHA);
} else {
mMoreIcon.setVisibility(GONE);
mIcon.setAlpha(UNSELECTED_ALPHA);
}
}
}

View File

@@ -0,0 +1,114 @@
package com.android.systemui.statusbar.car;
import android.app.ActivityManager;
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;
import java.util.Set;
/**
* 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.
*/
public class CarFacetButtonController {
protected HashMap<String, CarFacetButton> mButtonsByCategory = new HashMap<>();
protected HashMap<String, CarFacetButton> mButtonsByPackage = new HashMap<>();
protected CarFacetButton mSelectedFacetButton;
protected Context mContext;
public CarFacetButtonController(Context context) {
mContext = context;
}
/**
* 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
*/
public void addCarNavigationBar(CarNavigationBarView bar) {
findFacets(bar);
}
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);
}
}
}
/**
* 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 taskino.
* @param taskInfo of the currently running application
*/
public void taskChanged(ActivityManager.RunningTaskInfo taskInfo) {
if (taskInfo == null || taskInfo.baseActivity == null) {
return;
}
String packageName = taskInfo.baseActivity.getPackageName();
// If the package name belongs to a filter, then highlight appropriate button in
// the navigation bar.
if (mSelectedFacetButton != null) {
mSelectedFacetButton.setSelected(false);
}
CarFacetButton facetButton = mButtonsByPackage.get(packageName);
if (facetButton != null) {
facetButton.setSelected(true);
mSelectedFacetButton = facetButton;
} else {
String category = getPackageCategory(packageName);
if (category != null) {
facetButton = mButtonsByCategory.get(category);
facetButton.setSelected(true);
mSelectedFacetButton = facetButton;
}
}
}
protected String getPackageCategory(String packageName) {
PackageManager pm = mContext.getPackageManager();
Set<String> supportedCategories = mButtonsByCategory.keySet();
for (String category : supportedCategories) {
Intent intent = new Intent();
intent.setPackage(packageName);
intent.setAction(Intent.ACTION_MAIN);
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
// all categories next time this package name shows up.
mButtonsByPackage.put(packageName, mButtonsByCategory.get(category));
return category;
}
}
return null;
}
}

View File

@@ -1,407 +0,0 @@
/*
* Copyright (C) 2015 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;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.support.v4.util.SimpleArrayMap;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseBooleanArray;
import android.view.View;
import android.widget.LinearLayout;
import com.android.systemui.R;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
/**
* A controller to populate data for CarNavigationBarView and handle user interactions.
*
* <p>Each button inside the navigation bar is defined by data in arrays_car.xml. OEMs can
* customize the navigation buttons by updating arrays_car.xml appropriately in an overlay.
*/
class CarNavigationBarController {
private static final String TAG = "CarNavBarController";
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";
/**
* Each facet of the navigation bar maps to a set of package names or categories defined in
* arrays_car.xml. Package names for a given facet are delimited by ";".
*/
private static final String FACET_FILTER_DELIMITER = ";";
private final Context mContext;
private final CarNavigationBarView mNavBar;
private final CarStatusBar mStatusBar;
/**
* Set of categories each facet will filter on.
*/
private final List<String[]> mFacetCategories = new ArrayList<>();
/**
* Set of package names each facet will filter on.
*/
private final List<String[]> mFacetPackages = new ArrayList<>();
private final SimpleArrayMap<String, Integer> mFacetCategoryMap = new SimpleArrayMap<>();
private final SimpleArrayMap<String, Integer> mFacetPackageMap = new SimpleArrayMap<>();
private final List<CarNavigationButton> mNavButtons = new ArrayList<>();
private final SparseBooleanArray mFacetHasMultipleAppsCache = new SparseBooleanArray();
private int mCurrentFacetIndex;
private Intent mPersistentTaskIntent;
public CarNavigationBarController(Context context, CarNavigationBarView navBar,
CarStatusBar activityStarter) {
mContext = context;
mNavBar = navBar;
mStatusBar = activityStarter;
bind();
if (context.getResources().getBoolean(R.bool.config_enablePersistentDockedActivity)) {
setupPersistentDockedTask();
}
}
private void setupPersistentDockedTask() {
try {
mPersistentTaskIntent = Intent.parseUri(
mContext.getString(R.string.config_persistentDockedActivityIntentUri),
Intent.URI_INTENT_SCHEME);
} catch (URISyntaxException e) {
Log.e(TAG, "Malformed persistent task intent.");
}
}
public void taskChanged(String packageName, ActivityManager.RunningTaskInfo taskInfo) {
// If the package name belongs to a filter, then highlight appropriate button in
// the navigation bar.
if (mFacetPackageMap.containsKey(packageName)) {
setCurrentFacet(mFacetPackageMap.get(packageName));
}
// Check if the package matches any of the categories for the facets
String category = getPackageCategory(packageName);
if (category != null) {
setCurrentFacet(mFacetCategoryMap.get(category));
}
// Set up the persistent docked task if needed.
boolean isHomeTask =
taskInfo.configuration.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME;
if (mPersistentTaskIntent != null && !mStatusBar.hasDockedTask() && !isHomeTask) {
mStatusBar.startActivityOnStack(mPersistentTaskIntent,
WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
}
}
public void onPackageChange(String packageName) {
if (mFacetPackageMap.containsKey(packageName)) {
int index = mFacetPackageMap.get(packageName);
mFacetHasMultipleAppsCache.put(index, facetHasMultiplePackages(index));
// No need to check categories because we've already refreshed the cache.
return;
}
String category = getPackageCategory(packageName);
if (mFacetCategoryMap.containsKey(category)) {
int index = mFacetCategoryMap.get(category);
mFacetHasMultipleAppsCache.put(index, facetHasMultiplePackages(index));
}
}
/**
* Iterates through the items in arrays_car.xml and sets up the facet bar buttons to
* perform the task in that configuration file when clicked or long-pressed.
*/
private void bind() {
Resources res = mContext.getResources();
TypedArray icons = res.obtainTypedArray(R.array.car_facet_icons);
TypedArray intents = res.obtainTypedArray(R.array.car_facet_intent_uris);
TypedArray longPressIntents = res.obtainTypedArray(R.array.car_facet_longpress_intent_uris);
TypedArray facetPackageNames = res.obtainTypedArray(R.array.car_facet_package_filters);
TypedArray facetCategories = res.obtainTypedArray(R.array.car_facet_category_filters);
try {
if (icons.length() != intents.length()
|| icons.length() != longPressIntents.length()
|| icons.length() != facetPackageNames.length()
|| icons.length() != facetCategories.length()) {
throw new RuntimeException("car_facet array lengths do not match");
}
for (int i = 0, size = icons.length(); i < size; i++) {
Drawable icon = icons.getDrawable(i);
CarNavigationButton button = createNavButton(icon);
initClickListeners(button, i, intents.getString(i), longPressIntents.getString(i));
mNavButtons.add(button);
mNavBar.addButton(button, createNavButton(icon) /* lightsOutButton */);
initFacetFilterMaps(i, facetPackageNames.getString(i).split(FACET_FILTER_DELIMITER),
facetCategories.getString(i).split(FACET_FILTER_DELIMITER));
mFacetHasMultipleAppsCache.put(i, facetHasMultiplePackages(i));
}
} finally {
// Clean up all the TypedArrays.
icons.recycle();
intents.recycle();
longPressIntents.recycle();
facetPackageNames.recycle();
facetCategories.recycle();
}
}
/**
* Recreates each of the buttons on a density or font scale change. This manual process is
* necessary since this class is not part of an activity that automatically gets recreated.
*/
public void onDensityOrFontScaleChanged() {
TypedArray icons = mContext.getResources().obtainTypedArray(R.array.car_facet_icons);
try {
int length = icons.length();
if (length != mNavButtons.size()) {
// This should not happen since the mNavButtons list is created from the length
// of the icons array in bind().
throw new RuntimeException("car_facet array lengths do not match number of "
+ "created buttons.");
}
for (int i = 0; i < length; i++) {
Drawable icon = icons.getDrawable(i);
// Setting a new icon will trigger a requestLayout() call if necessary.
mNavButtons.get(i).setResources(icon);
}
} finally {
icons.recycle();
}
}
private void initFacetFilterMaps(int id, String[] packageNames, String[] categories) {
mFacetCategories.add(categories);
for (String category : categories) {
mFacetCategoryMap.put(category, id);
}
mFacetPackages.add(packageNames);
for (String packageName : packageNames) {
mFacetPackageMap.put(packageName, id);
}
}
private String getPackageCategory(String packageName) {
PackageManager pm = mContext.getPackageManager();
int size = mFacetCategories.size();
// For each facet, check if the given package name matches one of its categories
for (int i = 0; i < size; i++) {
String[] categories = mFacetCategories.get(i);
for (int j = 0; j < categories.length; j++) {
String category = categories[j];
Intent intent = new Intent();
intent.setPackage(packageName);
intent.setAction(Intent.ACTION_MAIN);
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
// all categories next time this package name shows up.
mFacetPackageMap.put(packageName, mFacetCategoryMap.get(category));
return category;
}
}
}
return null;
}
/**
* Helper method to check if a given facet has multiple packages associated with it. This can
* be resource defined package names or package names filtered by facet category.
*
* @return {@code true} if the facet at the given index has more than one package.
*/
private boolean facetHasMultiplePackages(int index) {
PackageManager pm = mContext.getPackageManager();
// Check if the packages defined for the filter actually exists on the device
String[] packages = mFacetPackages.get(index);
if (packages.length > 1) {
int count = 0;
for (int i = 0; i < packages.length; i++) {
count += pm.getLaunchIntentForPackage(packages[i]) != null ? 1 : 0;
if (count > 1) {
return true;
}
}
}
// If there weren't multiple packages defined for the facet, check the categories
// and see if they resolve to multiple package names
String categories[] = mFacetCategories.get(index);
int count = 0;
for (int i = 0; i < categories.length; i++) {
String category = categories[i];
Intent intent = new Intent();
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(category);
count += pm.queryIntentActivities(intent, 0).size();
if (count > 1) {
return true;
}
}
return false;
}
/**
* Sets the facet at the given index to be the facet that is currently active. The button will
* be highlighted appropriately.
*/
private void setCurrentFacet(int index) {
if (index == mCurrentFacetIndex) {
return;
}
if (mNavButtons.get(mCurrentFacetIndex) != null) {
mNavButtons.get(mCurrentFacetIndex)
.setSelected(false /* selected */, false /* showMoreIcon */);
}
if (mNavButtons.get(index) != null) {
mNavButtons.get(index).setSelected(true /* selected */,
mFacetHasMultipleAppsCache.get(index) /* showMoreIcon */);
}
mCurrentFacetIndex = index;
}
/**
* Creates the View that is used for the buttons along the navigation bar.
*
* @param icon The icon to be used for the button.
*/
private CarNavigationButton createNavButton(Drawable icon) {
CarNavigationButton button = (CarNavigationButton) View.inflate(mContext,
R.layout.car_navigation_button, null);
button.setResources(icon);
LinearLayout.LayoutParams lp =
new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1);
button.setLayoutParams(lp);
return button;
}
/**
* Initializes the click and long click listeners that correspond to the given command string.
* The click listeners are attached to the given button.
*/
private void initClickListeners(View button, int index, String clickString,
String longPressString) {
// Each button at least have an action when pressed.
if (TextUtils.isEmpty(clickString)) {
throw new RuntimeException("Facet at index " + index + " does not have click action.");
}
try {
Intent intent = Intent.parseUri(clickString, Intent.URI_INTENT_SCHEME);
button.setOnClickListener(v -> onFacetClicked(intent, index));
} catch (URISyntaxException e) {
throw new RuntimeException("Malformed intent uri", e);
}
if (TextUtils.isEmpty(longPressString)) {
button.setLongClickable(false);
return;
}
try {
Intent intent = Intent.parseUri(longPressString, Intent.URI_INTENT_SCHEME);
button.setOnLongClickListener(v -> {
onFacetLongClicked(intent, index);
return true;
});
} catch (URISyntaxException e) {
throw new RuntimeException("Malformed long-press intent uri", e);
}
}
/**
* Handles a click on a facet. A click will trigger the given Intent.
*
* @param index The index of the facet that was clicked.
*/
private void onFacetClicked(Intent intent, int index) {
String packageName = intent.getPackage();
if (packageName == null && !intent.getCategories().contains(Intent.CATEGORY_HOME)) {
return;
}
intent.putExtra(EXTRA_FACET_CATEGORIES, mFacetCategories.get(index));
intent.putExtra(EXTRA_FACET_PACKAGES, mFacetPackages.get(index));
// The facet is identified by the index in which it was added to the nav bar.
// This value can be used to determine which facet was selected
intent.putExtra(EXTRA_FACET_ID, Integer.toString(index));
// If the current facet is clicked, we want to launch the picker by default
// rather than the "preferred/last run" app.
intent.putExtra(EXTRA_FACET_LAUNCH_PICKER, index == mCurrentFacetIndex);
int windowingMode = WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
int activityType = ACTIVITY_TYPE_UNDEFINED;
if (intent.getCategories().contains(Intent.CATEGORY_HOME)) {
windowingMode = WINDOWING_MODE_UNDEFINED;
activityType = ACTIVITY_TYPE_HOME;
}
setCurrentFacet(index);
mStatusBar.startActivityOnStack(intent, windowingMode, activityType);
}
/**
* Handles a long-press on a facet. The long-press will trigger the given Intent.
*
* @param index The index of the facet that was clicked.
*/
private void onFacetLongClicked(Intent intent, int index) {
setCurrentFacet(index);
mStatusBar.startActivityOnStack(intent,
WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_UNDEFINED);
}
}

View File

@@ -16,17 +16,15 @@
package com.android.systemui.statusbar.car;
import android.app.UiModeManager;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.util.Log;
import android.view.View;
import android.widget.LinearLayout;
import com.android.keyguard.AlphaOptimizedImageButton;
import com.android.systemui.R;
import com.android.systemui.plugins.statusbar.phone.NavGesture;
import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
import com.android.systemui.statusbar.phone.NavigationBarView;
/**
* A custom navigation bar for the automotive use case.
@@ -34,9 +32,10 @@ import com.android.systemui.statusbar.phone.NavigationBarView;
* The navigation bar in the automotive use case is more like a list of shortcuts, rendered
* in a linear layout.
*/
class CarNavigationBarView extends NavigationBarView {
class CarNavigationBarView extends LinearLayout {
private LinearLayout mNavButtons;
private LinearLayout mLightsOutButtons;
private AlphaOptimizedImageButton mNotificationsButton;
private CarStatusBar mCarStatusBar;
public CarNavigationBarView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -45,99 +44,16 @@ class CarNavigationBarView extends NavigationBarView {
@Override
public void onFinishInflate() {
mNavButtons = findViewById(R.id.nav_buttons);
mLightsOutButtons = findViewById(R.id.lights_out);
mNotificationsButton = findViewById(R.id.notifications);
mNotificationsButton.setOnClickListener(this::onNotificationsClick);
}
public void addButton(CarNavigationButton button, CarNavigationButton lightsOutButton){
mNavButtons.addView(button);
mLightsOutButtons.addView(lightsOutButton);
void setStatusBar(CarStatusBar carStatusBar) {
mCarStatusBar = carStatusBar;
}
@Override
public void setDisabledFlags(int disabledFlags, boolean force) {
// TODO: Populate.
}
@Override
public void reorient() {
// We expect the car head unit to always have a fixed rotation so we ignore this. The super
// class implentation expects mRotatedViews to be populated, so if you call into it, there
// is a possibility of a NullPointerException.
}
@Override
public View getCurrentView() {
return this;
}
@Override
public void setNavigationIconHints(int hints, boolean force) {
// We do not need to set the navigation icon hints for a vehicle
// Calling setNavigationIconHints in the base class will result in a NPE as the car
// navigation bar does not have a back button.
}
@Override
public void onPluginConnected(NavGesture plugin, Context context) {
// set to null version of the plugin ignoring incoming arg.
super.onPluginConnected(new NullNavGesture(), context);
}
@Override
public void onPluginDisconnected(NavGesture plugin) {
// reinstall the null nav gesture plugin
super.onPluginConnected(new NullNavGesture(), getContext());
}
/**
* Null object pattern to work around expectations of the base class.
* This is a temporary solution to have the car system ui working.
* Already underway is a refactor of they car sys ui as to not use this class
* hierarchy.
*/
private static class NullNavGesture implements NavGesture {
@Override
public GestureHelper getGestureHelper() {
return new GestureHelper() {
@Override
public boolean onTouchEvent(MotionEvent event) {
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return false;
}
@Override
public void setBarState(boolean vertical, boolean isRtl) {
}
@Override
public void onDraw(Canvas canvas) {
}
@Override
public void onDarkIntensityChange(float intensity) {
}
@Override
public void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
};
}
@Override
public int getVersion() {
return 0;
}
@Override
public void onCreate(Context sysuiContext, Context pluginContext) {
}
@Override
public void onDestroy() {
}
protected void onNotificationsClick(View v) {
mCarStatusBar.togglePanel();
}
}

View File

@@ -1,72 +1,87 @@
/*
* Copyright (C) 2015 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;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.content.Intent;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import com.android.keyguard.AlphaOptimizedImageButton;
import com.android.systemui.R;
import java.net.URISyntaxException;
/**
* A wrapper view for a car navigation facet, which includes a button icon and a drop down icon.
* CarNavigationButton is an image button that allows for a bit more configuration at the
* xml file level. This allows for more control via overlays instead of having to update
* code.
*/
public class CarNavigationButton extends RelativeLayout {
public class CarNavigationButton extends com.android.keyguard.AlphaOptimizedImageButton {
private static final float SELECTED_ALPHA = 1;
private static final float UNSELECTED_ALPHA = 0.7f;
private AlphaOptimizedImageButton mIcon;
private AlphaOptimizedImageButton mMoreIcon;
private Context mContext;
private String mIntent = null;
private String mLongIntent = null;
private boolean mBroadcastIntent = false;
private boolean mSelected = false;
public CarNavigationButton(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
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);
}
/**
* 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();
mIcon = findViewById(R.id.car_nav_button_icon);
mIcon.setScaleType(ImageView.ScaleType.CENTER);
mIcon.setClickable(false);
mIcon.setBackgroundColor(android.R.color.transparent);
mIcon.setAlpha(UNSELECTED_ALPHA);
setScaleType(ImageView.ScaleType.CENTER);
setAlpha(UNSELECTED_ALPHA);
try {
if (mIntent != null) {
final Intent intent = Intent.parseUri(mIntent, Intent.URI_INTENT_SCHEME);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
setOnClickListener(v -> {
if (mBroadcastIntent) {
mContext.sendBroadcast(intent);
return;
}
mContext.startActivity(intent);
});
}
} catch (URISyntaxException e) {
throw new RuntimeException("Failed to attach intent", e);
}
mMoreIcon = findViewById(R.id.car_nav_button_more_icon);
mMoreIcon.setClickable(false);
mMoreIcon.setBackgroundColor(android.R.color.transparent);
mMoreIcon.setVisibility(INVISIBLE);
mMoreIcon.setImageDrawable(getContext().getDrawable(R.drawable.car_ic_arrow));
mMoreIcon.setAlpha(UNSELECTED_ALPHA);
}
public void setResources(Drawable icon) {
mIcon.setImageDrawable(icon);
}
public void setSelected(boolean selected, boolean showMoreIcon) {
if (selected) {
mMoreIcon.setVisibility(showMoreIcon ? VISIBLE : INVISIBLE);
mMoreIcon.setAlpha(SELECTED_ALPHA);
mIcon.setAlpha(SELECTED_ALPHA);
} else {
mMoreIcon.setVisibility(INVISIBLE);
mIcon.setAlpha(UNSELECTED_ALPHA);
try {
if (mLongIntent != null) {
final Intent intent = Intent.parseUri(mLongIntent, Intent.URI_INTENT_SCHEME);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
setOnLongClickListener(v -> {
mContext.startActivity(intent);
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 ? SELECTED_ALPHA : UNSELECTED_ALPHA);
}
}

View File

@@ -18,17 +18,14 @@ package com.android.systemui.statusbar.car;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
@@ -46,10 +43,7 @@ import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.misc.SysUiTaskStackChangeListener;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.NavigationBarView;
@@ -69,7 +63,6 @@ public class CarStatusBar extends StatusBar implements
private TaskStackListenerImpl mTaskStackListener;
private CarNavigationBarController mController;
private FullscreenUserSwitcher mFullscreenUserSwitcher;
private CarBatteryController mCarBatteryController;
@@ -78,15 +71,23 @@ public class CarStatusBar extends StatusBar implements
private ConnectedDeviceSignalController mConnectedDeviceSignalController;
private ViewGroup mNavigationBarWindow;
private ViewGroup mLeftNavigationBarWindow;
private ViewGroup mRightNavigationBarWindow;
private CarNavigationBarView mNavigationBarView;
private CarNavigationBarView mLeftNavigationBarView;
private CarNavigationBarView mRightNavigationBarView;
private final Object mQueueLock = new Object();
private boolean mShowLeft;
private boolean mShowRight;
private boolean mShowBottom;
private CarFacetButtonController mCarFacetButtonController;
@Override
public void start() {
super.start();
mTaskStackListener = new TaskStackListenerImpl();
ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
registerPackageChangeReceivers();
mStackScroller.setScrollingEnabled(true);
@@ -104,6 +105,16 @@ public class CarStatusBar extends StatusBar implements
mNavigationBarView = null;
}
if (mLeftNavigationBarWindow != null) {
mWindowManager.removeViewImmediate(mLeftNavigationBarWindow);
mLeftNavigationBarView = null;
}
if (mRightNavigationBarWindow != null) {
mWindowManager.removeViewImmediate(mRightNavigationBarWindow);
mRightNavigationBarView = null;
}
super.destroy();
}
@@ -153,10 +164,36 @@ public class CarStatusBar extends StatusBar implements
@Override
protected void createNavigationBar() {
mCarFacetButtonController = new CarFacetButtonController(mContext);
if (mNavigationBarView != null) {
return;
}
mShowBottom = mContext.getResources().getBoolean(R.bool.config_enableBottomNavigationBar);
if (mShowBottom) {
buildBottomBar();
}
int widthForSides = mContext.getResources().getDimensionPixelSize(
R.dimen.navigation_bar_height_car_mode);
mShowLeft = mContext.getResources().getBoolean(R.bool.config_enableLeftNavigationBar);
if (mShowLeft) {
buildLeft(widthForSides);
}
mShowRight = mContext.getResources().getBoolean(R.bool.config_enableRightNavigationBar);
if (mShowRight) {
buildRight(widthForSides);
}
}
private void buildBottomBar() {
// SystemUI requires that the navigation bar view have a parent. Since the regular
// StatusBar inflates navigation_bar_window as this parent view, use the same view for the
// CarNavigationBarView.
@@ -171,17 +208,15 @@ public class CarStatusBar extends StatusBar implements
mNavigationBarView = (CarNavigationBarView) mNavigationBarWindow.getChildAt(0);
if (mNavigationBarView == null) {
Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar");
throw new RuntimeException("Unable to build botom nav bar due to missing layout");
}
mNavigationBarView.setStatusBar(this);
mController = new CarNavigationBarController(mContext, mNavigationBarView,
this /* ActivityStarter*/);
mNavigationBarView.getBarTransitions().setAlwaysOpaque(true);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
@@ -189,9 +224,74 @@ public class CarStatusBar extends StatusBar implements
lp.setTitle("CarNavigationBar");
lp.windowAnimations = 0;
mCarFacetButtonController.addCarNavigationBar(mNavigationBarView);
mWindowManager.addView(mNavigationBarWindow, lp);
}
private void buildLeft(int widthForSides) {
mLeftNavigationBarWindow = (ViewGroup) View.inflate(mContext,
R.layout.navigation_bar_window, null);
if (mLeftNavigationBarWindow == null) {
Log.e(TAG, "CarStatusBar failed inflate for R.layout.navigation_bar_window");
}
View.inflate(mContext, R.layout.car_left_navigation_bar, mLeftNavigationBarWindow);
mLeftNavigationBarView = (CarNavigationBarView) mLeftNavigationBarWindow.getChildAt(0);
if (mLeftNavigationBarView == null) {
Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar");
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,
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
PixelFormat.TRANSLUCENT);
leftlp.setTitle("LeftCarNavigationBar");
leftlp.windowAnimations = 0;
leftlp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
leftlp.gravity = Gravity.LEFT;
mWindowManager.addView(mLeftNavigationBarWindow, leftlp);
}
private void buildRight(int widthForSides) {
mRightNavigationBarWindow = (ViewGroup) View.inflate(mContext,
R.layout.navigation_bar_window, null);
if (mRightNavigationBarWindow == null) {
Log.e(TAG, "CarStatusBar failed inflate for R.layout.navigation_bar_window");
}
View.inflate(mContext, R.layout.car_right_navigation_bar, mRightNavigationBarWindow);
mRightNavigationBarView = (CarNavigationBarView) mRightNavigationBarWindow.getChildAt(0);
if (mRightNavigationBarView == null) {
Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar");
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,
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
PixelFormat.TRANSLUCENT);
rightlp.setTitle("RightCarNavigationBar");
rightlp.windowAnimations = 0;
rightlp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
rightlp.gravity = Gravity.RIGHT;
mWindowManager.addView(mRightNavigationBarWindow, rightlp);
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
//When executing dump() funciton simultaneously, we need to serialize them
@@ -204,8 +304,8 @@ public class CarStatusBar extends StatusBar implements
}
pw.print(" mTaskStackListener="); pw.println(mTaskStackListener);
pw.print(" mController=");
pw.println(mController);
pw.print(" mCarFacetButtonController=");
pw.println(mCarFacetButtonController);
pw.print(" mFullscreenUserSwitcher="); pw.println(mFullscreenUserSwitcher);
pw.print(" mCarBatteryController=");
pw.println(mCarBatteryController);
@@ -229,10 +329,6 @@ public class CarStatusBar extends StatusBar implements
}
}
@Override
public NavigationBarView getNavigationBarView() {
return mNavigationBarView;
}
@Override
public View getNavigationBarWindow() {
@@ -269,24 +365,6 @@ public class CarStatusBar extends StatusBar implements
}
}
private BroadcastReceiver mPackageChangeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getData() == null || mController == null) {
return;
}
String packageName = intent.getData().getSchemeSpecificPart();
mController.onPackageChange(packageName);
}
};
private void registerPackageChangeReceivers() {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
mContext.registerReceiver(mPackageChangeReceiver, filter);
}
public boolean hasDockedTask() {
return Recents.getSystemServices().hasDockedTask();
@@ -301,10 +379,7 @@ public class CarStatusBar extends StatusBar implements
public void onTaskStackChanged() {
ActivityManager.RunningTaskInfo runningTaskInfo =
ActivityManagerWrapper.getInstance().getRunningTask();
if (runningTaskInfo != null && runningTaskInfo.baseActivity != null) {
mController.taskChanged(runningTaskInfo.baseActivity.getPackageName(),
runningTaskInfo);
}
mCarFacetButtonController.taskChanged(runningTaskInfo);
}
}
@@ -346,33 +421,6 @@ public class CarStatusBar extends StatusBar implements
// Do nothing, we don't want to display media art in the lock screen for a car.
}
private int startActivityWithOptions(Intent intent, Bundle options) {
int result = ActivityManager.START_CANCELED;
try {
result = ActivityManager.getService().startActivityAsUser(null /* caller */,
mContext.getBasePackageName(),
intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
null /* resultTo*/,
null /* resultWho*/,
0 /* requestCode*/,
Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP,
null /* profilerInfo*/,
options,
UserHandle.CURRENT.getIdentifier());
} catch (RemoteException e) {
Log.w(TAG, "Unable to start activity", e);
}
return result;
}
public int startActivityOnStack(Intent intent, int windowingMode, int activityType) {
final ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchWindowingMode(windowingMode);
options.setLaunchActivityType(activityType);
return startActivityWithOptions(intent, options.toBundle());
}
@Override
public void animateExpandNotificationsPanel() {
@@ -390,8 +438,6 @@ public class CarStatusBar extends StatusBar implements
@Override
public void onDensityOrFontScaleChanged() {
super.onDensityOrFontScaleChanged();
mController.onDensityOrFontScaleChanged();
// Need to update the background on density changed in case the change was due to night
// mode.
mNotificationPanelBackground = getDefaultWallpaper();