--- /dev/null
+<!--
+ 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>
\ No newline at end of file
--- /dev/null
+<!--
+ 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>
\ No newline at end of file
--- /dev/null
+<!--
+ 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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
<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>
+
*/
-->
-<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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
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>
--- /dev/null
+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);
+ }
+ }
+}
--- /dev/null
+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;
+ }
+}
+++ /dev/null
-/*
- * 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);
- }
-}
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.
* 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);
@Override
public void onFinishInflate() {
mNavButtons = findViewById(R.id.nav_buttons);
- mLightsOutButtons = findViewById(R.id.lights_out);
- }
- public void addButton(CarNavigationButton button, CarNavigationButton lightsOutButton){
- mNavButtons.addView(button);
- mLightsOutButtons.addView(lightsOutButton);
+ mNotificationsButton = findViewById(R.id.notifications);
+ mNotificationsButton.setOnClickListener(this::onNotificationsClick);
}
- @Override
- public void setDisabledFlags(int disabledFlags, boolean force) {
- // TODO: Populate.
+ void setStatusBar(CarStatusBar carStatusBar) {
+ mCarStatusBar = carStatusBar;
}
- @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();
}
}
-/*
- * 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);
-
- 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);
- }
+ 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);
+ }
- public void setResources(Drawable icon) {
- mIcon.setImageDrawable(icon);
+ 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);
+ }
}
- 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);
- }
+ /**
+ * @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);
}
}
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;
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;
private TaskStackListenerImpl mTaskStackListener;
- private CarNavigationBarController mController;
private FullscreenUserSwitcher mFullscreenUserSwitcher;
private CarBatteryController mCarBatteryController;
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);
mNavigationBarView = null;
}
+ if (mLeftNavigationBarWindow != null) {
+ mWindowManager.removeViewImmediate(mLeftNavigationBarWindow);
+ mLeftNavigationBarView = null;
+ }
+
+ if (mRightNavigationBarWindow != null) {
+ mWindowManager.removeViewImmediate(mRightNavigationBarWindow);
+ mRightNavigationBarView = null;
+ }
+
super.destroy();
}
@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.
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,
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
}
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);
}
}
- @Override
- public NavigationBarView getNavigationBarView() {
- return mNavigationBarView;
- }
@Override
public View getNavigationBarWindow() {
}
}
- 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();
public void onTaskStackChanged() {
ActivityManager.RunningTaskInfo runningTaskInfo =
ActivityManagerWrapper.getInstance().getRunningTask();
- if (runningTaskInfo != null && runningTaskInfo.baseActivity != null) {
- mController.taskChanged(runningTaskInfo.baseActivity.getPackageName(),
- runningTaskInfo);
- }
+ mCarFacetButtonController.taskChanged(runningTaskInfo);
}
}
// 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() {
@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();