Refactor notification to allow for overlay by Android Auto.
Android Auto requires different styling for notifications. In particular: - Background of scrim to be a different color - Notification shade should always be full width - No quick settings - User switcher always visible - Card styling, such as rounded corners and more spacing between items. - Notification shade to be triggered from a button on the navigation bar. Refactored different areas of the framework to allow for this. This mostly means extending notification code and overriding methods where appropriate. Test: manually tested Bug: 33487455 Change-Id: Ifcea70e121c1cbbeb057bb6ce8d351336ae41554
This commit is contained in:
@@ -2,23 +2,22 @@
|
||||
<!--
|
||||
** Copyright 2012, 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
|
||||
** 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
|
||||
** 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
|
||||
** 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.
|
||||
-->
|
||||
|
||||
<!-- Extends RelativeLayout -->
|
||||
<com.android.systemui.statusbar.phone.QuickStatusBarHeader
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:systemui="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/status_bar_header_height"
|
||||
@@ -29,8 +28,7 @@
|
||||
android:clipToPadding="false"
|
||||
android:paddingTop="0dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:paddingStart="0dp"
|
||||
>
|
||||
android:paddingStart="0dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
@@ -190,8 +188,7 @@
|
||||
android:layout_alignParentBottom="true"
|
||||
android:alpha="0"
|
||||
android:background="@color/qs_detail_progress_track"
|
||||
android:src="@drawable/indeterminate_anim"
|
||||
/>
|
||||
android:src="@drawable/indeterminate_anim"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/header_debug_info"
|
||||
@@ -203,7 +200,6 @@
|
||||
android:textColor="#00A040"
|
||||
android:textSize="11dp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="invisible"
|
||||
/>
|
||||
android:visibility="invisible"/>
|
||||
|
||||
</com.android.systemui.statusbar.phone.QuickStatusBarHeader>
|
||||
|
||||
@@ -3,28 +3,27 @@
|
||||
**
|
||||
** Copyright 2006, 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
|
||||
** 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
|
||||
** 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
|
||||
** 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.phone.NotificationPanelView
|
||||
<com.android.systemui.statusbar.phone.NotificationPanelView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:systemui="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/notification_panel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/transparent"
|
||||
>
|
||||
android:background="@android:color/transparent" >
|
||||
|
||||
<include
|
||||
layout="@layout/keyguard_status_view"
|
||||
@@ -77,8 +76,8 @@
|
||||
</com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer>
|
||||
|
||||
<include
|
||||
layout="@layout/keyguard_bottom_area"
|
||||
android:visibility="gone" />
|
||||
layout="@layout/keyguard_bottom_area"
|
||||
android:visibility="gone" />
|
||||
|
||||
<com.android.systemui.statusbar.AlphaOptimizedView
|
||||
android:id="@+id/qs_navbar_scrim"
|
||||
@@ -88,4 +87,4 @@
|
||||
android:visibility="invisible"
|
||||
android:background="@drawable/qs_navbar_scrim" />
|
||||
|
||||
</com.android.systemui.statusbar.phone.NotificationPanelView><!-- end of sliding panel -->
|
||||
</com.android.systemui.statusbar.phone.NotificationPanelView>
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:importantForAccessibility="no"
|
||||
sysui:ignoreRightInset="true"
|
||||
sysui:scrimColor="@color/scrim_behind_color"
|
||||
/>
|
||||
|
||||
<com.android.systemui.statusbar.AlphaOptimizedView
|
||||
|
||||
@@ -115,5 +115,9 @@
|
||||
<declare-styleable name="PluginInflateContainer">
|
||||
<attr name="viewType" format="string" />
|
||||
</declare-styleable>
|
||||
<declare-styleable name="ScrimView">
|
||||
<!-- The initial color for the scrim. -->
|
||||
<attr name="scrimColor" format="color" />
|
||||
</declare-styleable>
|
||||
</resources>
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
<color name="qs_detail_button">@*android:color/quaternary_device_default_settings</color>
|
||||
<color name="qs_detail_button_white">#B3FFFFFF</color><!-- 70% white -->
|
||||
<color name="qs_detail_transition">#66FFFFFF</color>
|
||||
<color name="scrim_behind_color">@android:color/black</color>
|
||||
<color name="status_bar_clock_color">#FFFFFFFF</color>
|
||||
<color name="qs_user_detail_icon_muted">#FFFFFFFF</color> <!-- not so muted after all -->
|
||||
<color name="qs_tile_disabled_color">#9E9E9E</color> <!-- 38% black -->
|
||||
|
||||
@@ -302,4 +302,34 @@
|
||||
<item type="id" name="action_split_task_to_right" />
|
||||
<item type="id" name="action_split_task_to_top" />
|
||||
|
||||
<!-- Whether or not the gear icon on notifications should be shown. The gear is shown when the
|
||||
the notification is not swiped enough to dismiss it. -->
|
||||
<bool name="config_showNotificationGear">true</bool>
|
||||
|
||||
<!-- Whether or not a background should be drawn behind a notification. -->
|
||||
<bool name="config_drawNotificationBackground">true</bool>
|
||||
|
||||
<!-- Whether or not the edit icon on the quick settings header is shown. -->
|
||||
<bool name="config_showQuickSettingsEditingIcon">true</bool>
|
||||
|
||||
<!-- Whether or not the multi-user switcher should be visible even if the quick settings are
|
||||
not expanded. If there are not multiple users on the system, the switcher will still
|
||||
hide itself. -->
|
||||
<bool name="config_alwaysShowMultiUserSwitcher">false</bool>
|
||||
|
||||
<!-- Whether or not the expand indicator is visible for manually expanding the quick settings
|
||||
panel. -->
|
||||
<bool name="config_showQuickSettingsExpandIndicator">true</bool>
|
||||
|
||||
<!-- Whether or not to display the row of quick settings icons separate from the full quick
|
||||
settings panel. -->
|
||||
<bool name="config_showQuickSettingsRow">true</bool>
|
||||
|
||||
<!-- Whether or not the quick settings should be revealed on an overscroll of the
|
||||
notifications panel. -->
|
||||
<bool name="config_enableQuickSettingsOverscrollExpansion">true</bool>
|
||||
|
||||
<!-- Whether or the notifications can be shown and dismissed with a drag. -->
|
||||
<bool name="config_enableNotificationShadeDrag">true</bool>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -347,6 +347,8 @@
|
||||
<dimen name="keyguard_clock_notifications_margin_max">42dp</dimen>
|
||||
<dimen name="heads_up_scrim_height">250dp</dimen>
|
||||
|
||||
<item name="scrim_behind_alpha" format="float" type="dimen">0.62</item>
|
||||
|
||||
<!-- The minimum amount the user needs to swipe to go to the camera / phone. -->
|
||||
<dimen name="keyguard_min_swipe_amount">110dp</dimen>
|
||||
|
||||
|
||||
@@ -74,8 +74,14 @@ public class QSFragment extends Fragment implements QS {
|
||||
mContainer = (QSContainerImpl) view;
|
||||
|
||||
mQSDetail.setQsPanel(mQSPanel, mHeader);
|
||||
mQSAnimator = new QSAnimator(this, (QuickQSPanel) mHeader.findViewById(R.id.quick_qs_panel),
|
||||
mQSPanel);
|
||||
|
||||
// If the quick settings row is not shown, then there is no need for the animation from
|
||||
// the row to the full QS panel.
|
||||
if (getResources().getBoolean(R.bool.config_showQuickSettingsRow)) {
|
||||
mQSAnimator = new QSAnimator(this,
|
||||
(QuickQSPanel) mHeader.findViewById(R.id.quick_qs_panel), mQSPanel);
|
||||
}
|
||||
|
||||
mQSCustomizer = (QSCustomizer) view.findViewById(R.id.qs_customize);
|
||||
mQSCustomizer.setQs(this);
|
||||
}
|
||||
@@ -89,7 +95,10 @@ public class QSFragment extends Fragment implements QS {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
if (newConfig.getLayoutDirection() != mLayoutDirection) {
|
||||
mLayoutDirection = newConfig.getLayoutDirection();
|
||||
mQSAnimator.onRtlChanged();
|
||||
|
||||
if (mQSAnimator != null) {
|
||||
mQSAnimator.onRtlChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +117,10 @@ public class QSFragment extends Fragment implements QS {
|
||||
mQSPanel.setHost(qsh, mQSCustomizer);
|
||||
mHeader.setQSPanel(mQSPanel);
|
||||
mQSDetail.setHost(qsh);
|
||||
mQSAnimator.setHost(qsh);
|
||||
|
||||
if (mQSAnimator != null) {
|
||||
mQSAnimator.setHost(qsh);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateQsState() {
|
||||
@@ -155,7 +167,11 @@ public class QSFragment extends Fragment implements QS {
|
||||
public void setKeyguardShowing(boolean keyguardShowing) {
|
||||
if (DEBUG) Log.d(TAG, "setKeyguardShowing " + keyguardShowing);
|
||||
mKeyguardShowing = keyguardShowing;
|
||||
mQSAnimator.setOnKeyguard(keyguardShowing);
|
||||
|
||||
if (mQSAnimator != null) {
|
||||
mQSAnimator.setOnKeyguard(keyguardShowing);
|
||||
}
|
||||
|
||||
updateQsState();
|
||||
}
|
||||
|
||||
@@ -187,7 +203,10 @@ public class QSFragment extends Fragment implements QS {
|
||||
mHeader.setExpansion(mKeyguardShowing ? 1 : expansion);
|
||||
mQSPanel.setTranslationY(translationScaleY * mQSPanel.getHeight());
|
||||
mQSDetail.setFullyExpanded(expansion == 1);
|
||||
mQSAnimator.setPosition(expansion);
|
||||
|
||||
if (mQSAnimator != null) {
|
||||
mQSAnimator.setPosition(expansion);
|
||||
}
|
||||
|
||||
// Set bounds on the QS panel so it doesn't run over the header.
|
||||
mQsBounds.top = (int) (mQSPanel.getHeight() * (1 - expansion));
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
@@ -29,6 +30,7 @@ import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.animation.Interpolator;
|
||||
import com.android.systemui.R;
|
||||
|
||||
/**
|
||||
* A view which can draw a scrim
|
||||
@@ -73,6 +75,14 @@ public class ScrimView extends View
|
||||
|
||||
public ScrimView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
|
||||
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ScrimView);
|
||||
|
||||
try {
|
||||
mScrimColor = ta.getColor(R.styleable.ScrimView_scrimColor, Color.BLACK);
|
||||
} finally {
|
||||
ta.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -24,24 +24,22 @@ 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 com.android.systemui.ActivityStarter;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
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.
|
||||
*
|
||||
* <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";
|
||||
@@ -51,48 +49,49 @@ class CarNavigationBarController {
|
||||
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_DEMILITER = ";";
|
||||
/**
|
||||
* 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 Context mContext;
|
||||
private CarNavigationBarView mNavBar;
|
||||
private CarStatusBar mStatusBar;
|
||||
private final Context mContext;
|
||||
private final CarNavigationBarView mNavBar;
|
||||
private final CarStatusBar mStatusBar;
|
||||
|
||||
// Set of categories each facet will filter on.
|
||||
private List<String[]> mFacetCategories = new ArrayList<String[]>();
|
||||
// Set of package names each facet will filter on.
|
||||
private List<String[]> mFacetPackages = new ArrayList<String[]>();
|
||||
/**
|
||||
* Set of categories each facet will filter on.
|
||||
*/
|
||||
private final List<String[]> mFacetCategories = new ArrayList<>();
|
||||
|
||||
private SimpleArrayMap<String, Integer> mFacetCategoryMap
|
||||
= new SimpleArrayMap<String, Integer>();
|
||||
private SimpleArrayMap<String, Integer> mFacetPackageMap
|
||||
= new SimpleArrayMap<String, Integer>();
|
||||
/**
|
||||
* Set of package names each facet will filter on.
|
||||
*/
|
||||
private final List<String[]> mFacetPackages = new ArrayList<>();
|
||||
|
||||
private List<Intent> mIntents;
|
||||
private List<Intent> mLongPressIntents;
|
||||
private final SimpleArrayMap<String, Integer> mFacetCategoryMap = new SimpleArrayMap<>();
|
||||
private final SimpleArrayMap<String, Integer> mFacetPackageMap = new SimpleArrayMap<>();
|
||||
|
||||
private List<CarNavigationButton> mNavButtons = new ArrayList<CarNavigationButton>();
|
||||
private final List<CarNavigationButton> mNavButtons = new ArrayList<>();
|
||||
|
||||
private final SparseBooleanArray mFacetHasMultipleAppsCache = new SparseBooleanArray();
|
||||
|
||||
private int mCurrentFacetIndex;
|
||||
private SparseBooleanArray mFacetHasMultipleAppsCache = new SparseBooleanArray();
|
||||
|
||||
private Intent mPersistentTaskIntent;
|
||||
|
||||
public CarNavigationBarController(Context context,
|
||||
CarNavigationBarView navBar,
|
||||
CarStatusBar activityStarter) {
|
||||
public CarNavigationBarController(Context context, CarNavigationBarView navBar,
|
||||
CarStatusBar activityStarter) {
|
||||
mContext = context;
|
||||
mNavBar = navBar;
|
||||
mStatusBar = activityStarter;
|
||||
bind();
|
||||
|
||||
if (context.getResources().getBoolean(R.bool.config_enablePersistentDockedActivity)) {
|
||||
setupPersistentDockedTask(context);
|
||||
setupPersistentDockedTask();
|
||||
}
|
||||
}
|
||||
|
||||
private void setupPersistentDockedTask(Context context) {
|
||||
private void setupPersistentDockedTask() {
|
||||
try {
|
||||
mPersistentTaskIntent = Intent.parseUri(
|
||||
mContext.getString(R.string.config_persistentDockedActivityIntentUri),
|
||||
@@ -137,64 +136,85 @@ class CarNavigationBarController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
// Read up arrays_car.xml and populate the navigation bar here.
|
||||
Resources r = mContext.getResources();
|
||||
TypedArray icons = r.obtainTypedArray(R.array.car_facet_icons);
|
||||
TypedArray intents = r.obtainTypedArray(R.array.car_facet_intent_uris);
|
||||
TypedArray longpressIntents =
|
||||
r.obtainTypedArray(R.array.car_facet_longpress_intent_uris);
|
||||
TypedArray facetPackageNames = r.obtainTypedArray(R.array.car_facet_package_filters);
|
||||
Resources res = mContext.getResources();
|
||||
|
||||
TypedArray facetCategories = r.obtainTypedArray(R.array.car_facet_category_filters);
|
||||
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);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
mIntents = createEmptyIntentList(icons.length());
|
||||
mLongPressIntents = createEmptyIntentList(icons.length());
|
||||
|
||||
for (int i = 0; i < icons.length(); i++) {
|
||||
Drawable icon = icons.getDrawable(i);
|
||||
try {
|
||||
mIntents.set(i,
|
||||
Intent.parseUri(intents.getString(i), Intent.URI_INTENT_SCHEME));
|
||||
|
||||
String longpressUri = longpressIntents.getString(i);
|
||||
boolean hasLongpress = !longpressUri.isEmpty();
|
||||
if (hasLongpress) {
|
||||
mLongPressIntents.set(i,
|
||||
Intent.parseUri(longpressUri, Intent.URI_INTENT_SCHEME));
|
||||
}
|
||||
|
||||
CarNavigationButton button = createNavButton(icon, i, hasLongpress);
|
||||
mNavButtons.add(button);
|
||||
mNavBar.addButton(button,
|
||||
createNavButton(icon, i, hasLongpress) /* lightsOutButton */);
|
||||
|
||||
initFacetFilterMaps(i,
|
||||
facetPackageNames.getString(i).split(FACET_FILTER_DEMILITER),
|
||||
facetCategories.getString(i).split(FACET_FILTER_DEMILITER));
|
||||
mFacetHasMultipleAppsCache.put(i, facetHasMultiplePackages(i));
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException("Malformed intent uri", e);
|
||||
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 (int i = 0; i < categories.length; i++) {
|
||||
mFacetCategoryMap.put(categories[i], id);
|
||||
for (String category : categories) {
|
||||
mFacetCategoryMap.put(category, id);
|
||||
}
|
||||
|
||||
mFacetPackages.add(packageNames);
|
||||
for (int i = 0; i < packageNames.length; i++) {
|
||||
mFacetPackageMap.put(packageNames[i], id);
|
||||
for (String packageName : packageNames) {
|
||||
mFacetPackageMap.put(packageName, id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,8 +243,10 @@ class CarNavigationBarController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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();
|
||||
@@ -259,6 +281,10 @@ class CarNavigationBarController {
|
||||
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;
|
||||
@@ -273,11 +299,16 @@ class CarNavigationBarController {
|
||||
mNavButtons.get(index).setSelected(true /* selected */,
|
||||
mFacetHasMultipleAppsCache.get(index) /* showMoreIcon */);
|
||||
}
|
||||
|
||||
mCurrentFacetIndex = index;
|
||||
}
|
||||
|
||||
private CarNavigationButton createNavButton(Drawable icon, final int id,
|
||||
boolean longClickEnabled) {
|
||||
/**
|
||||
* 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);
|
||||
@@ -285,37 +316,49 @@ class CarNavigationBarController {
|
||||
new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1);
|
||||
button.setLayoutParams(lp);
|
||||
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
onFacetClicked(id);
|
||||
}
|
||||
});
|
||||
|
||||
if (longClickEnabled) {
|
||||
button.setLongClickable(true);
|
||||
button.setOnLongClickListener(new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
onFacetLongClicked(id);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
button.setLongClickable(false);
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
private void startActivity(Intent intent, ActivityStarter.Callback callback) {
|
||||
if (mStatusBar != null && intent != null) {
|
||||
mStatusBar.startActivity(intent, false, callback);
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
private void onFacetClicked(int index) {
|
||||
Intent intent = mIntents.get(index);
|
||||
/**
|
||||
* 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) {
|
||||
@@ -341,13 +384,13 @@ class CarNavigationBarController {
|
||||
mStatusBar.startActivityOnStack(intent, stackId);
|
||||
}
|
||||
|
||||
private void onFacetLongClicked(int index) {
|
||||
/**
|
||||
* 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(mLongPressIntents.get(index),
|
||||
StackId.FULLSCREEN_WORKSPACE_STACK_ID);
|
||||
}
|
||||
|
||||
private List<Intent> createEmptyIntentList(int size) {
|
||||
return Arrays.asList(new Intent[size]);
|
||||
mStatusBar.startActivityOnStack(intent, StackId.FULLSCREEN_WORKSPACE_STACK_ID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,10 +35,12 @@ import android.widget.LinearLayout;
|
||||
import com.android.systemui.BatteryMeterView;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SwipeHelper;
|
||||
import com.android.systemui.recents.Recents;
|
||||
import com.android.systemui.recents.misc.SystemServicesProxy;
|
||||
import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
|
||||
import com.android.systemui.statusbar.StatusBarState;
|
||||
import com.android.systemui.statusbar.phone.NavigationBarView;
|
||||
import com.android.systemui.statusbar.phone.StatusBar;
|
||||
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
|
||||
import com.android.systemui.statusbar.policy.BatteryController;
|
||||
@@ -51,10 +53,8 @@ public class CarStatusBar extends StatusBar implements
|
||||
CarBatteryController.BatteryViewHandler {
|
||||
private static final String TAG = "CarStatusBar";
|
||||
|
||||
private SystemServicesProxy mSystemServicesProxy;
|
||||
private TaskStackListenerImpl mTaskStackListener;
|
||||
|
||||
private CarNavigationBarView mCarNavigationBar;
|
||||
private CarNavigationBarController mController;
|
||||
private FullscreenUserSwitcher mFullscreenUserSwitcher;
|
||||
|
||||
@@ -62,7 +62,6 @@ public class CarStatusBar extends StatusBar implements
|
||||
private BatteryMeterView mBatteryMeterView;
|
||||
|
||||
private ConnectedDeviceSignalController mConnectedDeviceSignalController;
|
||||
private View mSignalsView;
|
||||
private CarNavigationBarView mNavigationBarView;
|
||||
|
||||
@Override
|
||||
@@ -72,6 +71,8 @@ public class CarStatusBar extends StatusBar implements
|
||||
SystemServicesProxy.getInstance(mContext).registerTaskStackListener(mTaskStackListener);
|
||||
registerPackageChangeReceivers();
|
||||
|
||||
mStackScroller.setScrollingEnabled(true);
|
||||
|
||||
createBatteryController();
|
||||
mCarBatteryController.startListening();
|
||||
mConnectedDeviceSignalController.startListening();
|
||||
@@ -96,16 +97,16 @@ public class CarStatusBar extends StatusBar implements
|
||||
mBatteryMeterView.setVisibility(View.GONE);
|
||||
|
||||
ViewStub stub = (ViewStub) statusBarView.findViewById(R.id.connected_device_signals_stub);
|
||||
mSignalsView = stub.inflate();
|
||||
View signalsView = stub.inflate();
|
||||
|
||||
// When a ViewStub if inflated, it does not respect the margins on the inflated view.
|
||||
// As a result, manually add the ending margin.
|
||||
((LinearLayout.LayoutParams) mSignalsView.getLayoutParams()).setMarginEnd(
|
||||
((LinearLayout.LayoutParams) signalsView.getLayoutParams()).setMarginEnd(
|
||||
mContext.getResources().getDimensionPixelOffset(
|
||||
R.dimen.status_bar_connected_device_signal_margin_end));
|
||||
|
||||
mConnectedDeviceSignalController = new ConnectedDeviceSignalController(mContext,
|
||||
mSignalsView);
|
||||
signalsView);
|
||||
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "makeStatusBarView(). mBatteryMeterView: " + mBatteryMeterView);
|
||||
@@ -126,12 +127,11 @@ public class CarStatusBar extends StatusBar implements
|
||||
return;
|
||||
}
|
||||
|
||||
mCarNavigationBar =
|
||||
(CarNavigationBarView) View.inflate(mContext, R.layout.car_navigation_bar, null);
|
||||
mController = new CarNavigationBarController(mContext, mCarNavigationBar,
|
||||
mNavigationBarView = (CarNavigationBarView) View.inflate(mContext,
|
||||
R.layout.car_navigation_bar, null);
|
||||
mController = new CarNavigationBarController(mContext, mNavigationBarView,
|
||||
this /* ActivityStarter*/);
|
||||
mNavigationBarView = mCarNavigationBar;
|
||||
mCarNavigationBar.getBarTransitions().setAlwaysOpaque(true);
|
||||
mNavigationBarView.getBarTransitions().setAlwaysOpaque(true);
|
||||
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
|
||||
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
|
||||
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
|
||||
@@ -147,6 +147,31 @@ public class CarStatusBar extends StatusBar implements
|
||||
mWindowManager.addView(mNavigationBarView, lp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NavigationBarView getNavigationBarView() {
|
||||
return mNavigationBarView;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View.OnTouchListener getStatusBarWindowTouchListener() {
|
||||
// Usually, a touch on the background window will dismiss the notification shade. However,
|
||||
// for the car use-case, the shade should remain unless the user switches to a different
|
||||
// facet (e.g. phone).
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link com.android.systemui.SwipeHelper.LongPressListener} that will be
|
||||
* triggered when a notification card is long-pressed.
|
||||
*/
|
||||
@Override
|
||||
protected SwipeHelper.LongPressListener getNotificationLongClicker() {
|
||||
// For the automative use case, we do not want to the user to be able to interact with
|
||||
// a notification other than a regular click. As a result, just return null for the
|
||||
// long click listener.
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showBatteryView() {
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
@@ -272,4 +297,14 @@ public class CarStatusBar extends StatusBar implements
|
||||
options.setLaunchStackId(stackId);
|
||||
return startActivityWithOptions(intent, options.toBundle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that relevant child views are appropriately recreated when the device's density
|
||||
* changes.
|
||||
*/
|
||||
@Override
|
||||
protected void onDensityOrFontScaleChanged() {
|
||||
super.onDensityOrFontScaleChanged();
|
||||
mController.onDensityOrFontScaleChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.app.StatusBarManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
@@ -137,6 +138,7 @@ public class NotificationPanelView extends PanelView implements
|
||||
protected int mQsMinExpansionHeight;
|
||||
protected int mQsMaxExpansionHeight;
|
||||
private int mQsPeekHeight;
|
||||
private boolean mQsOverscrollExpansionEnabled;
|
||||
private boolean mStackScrollerOverscrolling;
|
||||
private boolean mQsExpansionFromOverscroll;
|
||||
private float mLastOverscroll;
|
||||
@@ -217,6 +219,8 @@ public class NotificationPanelView extends PanelView implements
|
||||
super(context, attrs);
|
||||
setWillNotDraw(!DEBUG);
|
||||
mFalsingManager = FalsingManager.getInstance(context);
|
||||
mQsOverscrollExpansionEnabled =
|
||||
getResources().getBoolean(R.bool.config_enableQuickSettingsOverscrollExpansion);
|
||||
}
|
||||
|
||||
public void setStatusBar(StatusBar bar) {
|
||||
@@ -596,7 +600,8 @@ public class NotificationPanelView extends PanelView implements
|
||||
MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1);
|
||||
return true;
|
||||
}
|
||||
if (!isFullyCollapsed() && onQsIntercept(event)) {
|
||||
|
||||
if (mQsOverscrollExpansionEnabled && !isFullyCollapsed() && onQsIntercept(event)) {
|
||||
return true;
|
||||
}
|
||||
return super.onInterceptTouchEvent(event);
|
||||
@@ -770,7 +775,9 @@ public class NotificationPanelView extends PanelView implements
|
||||
return true;
|
||||
}
|
||||
mHeadsUpTouchHelper.onTouchEvent(event);
|
||||
if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
|
||||
|
||||
if (mQsOverscrollExpansionEnabled && !mHeadsUpTouchHelper.isTrackingHeadsUp()
|
||||
&& handleQsTouch(event)) {
|
||||
return true;
|
||||
}
|
||||
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
|
||||
@@ -953,6 +960,10 @@ public class NotificationPanelView extends PanelView implements
|
||||
|
||||
@Override
|
||||
public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
|
||||
if (!mQsOverscrollExpansionEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
cancelQsAnimation();
|
||||
if (!mQsExpansionEnabled) {
|
||||
amount = 0f;
|
||||
@@ -967,6 +978,10 @@ public class NotificationPanelView extends PanelView implements
|
||||
|
||||
@Override
|
||||
public void flingTopOverscroll(float velocity, boolean open) {
|
||||
if (!mQsOverscrollExpansionEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
mLastOverscroll = 0f;
|
||||
mQsExpansionFromOverscroll = false;
|
||||
setQsExpansion(mQsExpansionHeight);
|
||||
|
||||
@@ -110,6 +110,11 @@ public abstract class PanelView extends FrameLayout {
|
||||
private float mInitialTouchX;
|
||||
private boolean mTouchDisabled;
|
||||
|
||||
/**
|
||||
* Whether or not the PanelView can be expanded or collapsed with a drag.
|
||||
*/
|
||||
private boolean mNotificationsDragEnabled;
|
||||
|
||||
private Interpolator mBounceInterpolator;
|
||||
protected KeyguardBottomAreaView mKeyguardBottomArea;
|
||||
|
||||
@@ -190,6 +195,8 @@ public abstract class PanelView extends FrameLayout {
|
||||
0.84f /* y2 */);
|
||||
mBounceInterpolator = new BounceInterpolator();
|
||||
mFalsingManager = FalsingManager.getInstance(context);
|
||||
mNotificationsDragEnabled =
|
||||
getResources().getBoolean(R.bool.config_enableNotificationShadeDrag);
|
||||
}
|
||||
|
||||
protected void loadDimens() {
|
||||
@@ -232,6 +239,15 @@ public abstract class PanelView extends FrameLayout {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If dragging should not expand the notifications shade, then return false.
|
||||
if (!mNotificationsDragEnabled) {
|
||||
if (mTracking) {
|
||||
// Turn off tracking if it's on or the shade can get stuck in the down position.
|
||||
onTrackingStopped(true /* expand */);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// On expanding, single mouse click expands the panel instead of dragging.
|
||||
if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
@@ -487,7 +503,7 @@ public abstract class PanelView extends FrameLayout {
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent event) {
|
||||
if (mInstantExpanding
|
||||
if (mInstantExpanding || !mNotificationsDragEnabled
|
||||
|| (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -21,12 +21,14 @@ import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.RippleDrawable;
|
||||
import android.icu.text.NumberFormat;
|
||||
import android.os.UserManager;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.VisibleForTesting;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.SparseBooleanArray;
|
||||
@@ -67,9 +69,6 @@ import com.android.systemui.tuner.TunerService;
|
||||
public class QuickStatusBarHeader extends BaseStatusBarHeader implements
|
||||
NextAlarmChangeCallback, OnClickListener, OnUserInfoChangedListener, EmergencyListener,
|
||||
BatteryStateChangeCallback, SignalCallback {
|
||||
|
||||
private static final String TAG = "QuickStatusBarHeader";
|
||||
|
||||
private static final float EXPAND_INDICATOR_THRESHOLD = .93f;
|
||||
|
||||
private ActivityStarter mActivityStarter;
|
||||
@@ -99,13 +98,16 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements
|
||||
private boolean mShowEmergencyCallsOnly;
|
||||
protected MultiUserSwitch mMultiUserSwitch;
|
||||
private ImageView mMultiUserAvatar;
|
||||
|
||||
private boolean mAlwaysShowMultiUserSwitch;
|
||||
|
||||
private TouchAnimator mAnimator;
|
||||
protected TouchAnimator mSettingsAlpha;
|
||||
private float mExpansionAmount;
|
||||
protected QSTileHost mHost;
|
||||
|
||||
protected View mEdit;
|
||||
private boolean mShowEditIcon;
|
||||
|
||||
private boolean mShowFullAlarm;
|
||||
private float mDateTimeTranslation;
|
||||
private TextView mBatteryLevel;
|
||||
@@ -119,25 +121,37 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
Resources res = getResources();
|
||||
|
||||
mEmergencyOnly = (TextView) findViewById(R.id.header_emergency_calls_only);
|
||||
|
||||
mShowEditIcon = res.getBoolean(R.bool.config_showQuickSettingsEditingIcon);
|
||||
|
||||
mEdit = findViewById(android.R.id.edit);
|
||||
findViewById(android.R.id.edit).setOnClickListener(view ->
|
||||
Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() ->
|
||||
mQsPanel.showEdit(view)));
|
||||
mEdit.setVisibility(mShowEditIcon ? VISIBLE : GONE);
|
||||
|
||||
if (mShowEditIcon) {
|
||||
findViewById(android.R.id.edit).setOnClickListener(view ->
|
||||
Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() ->
|
||||
mQsPanel.showEdit(view)));
|
||||
}
|
||||
|
||||
mDateTimeAlarmGroup = (ViewGroup) findViewById(R.id.date_time_alarm_group);
|
||||
mDateTimeAlarmGroup.findViewById(R.id.empty_time_view).setVisibility(View.GONE);
|
||||
mDateTimeGroup = (ViewGroup) findViewById(R.id.date_time_group);
|
||||
mDateTimeGroup.setPivotX(0);
|
||||
mDateTimeGroup.setPivotY(0);
|
||||
mDateTimeTranslation = getResources().getDimension(R.dimen.qs_date_time_translation);
|
||||
mShowFullAlarm = getResources().getBoolean(R.bool.quick_settings_show_full_alarm);
|
||||
mDateTimeTranslation = res.getDimension(R.dimen.qs_date_time_translation);
|
||||
mShowFullAlarm = res.getBoolean(R.bool.quick_settings_show_full_alarm);
|
||||
|
||||
mExpandIndicator = (ExpandableIndicator) findViewById(R.id.expand_indicator);
|
||||
mExpandIndicator.setVisibility(
|
||||
res.getBoolean(R.bool.config_showQuickSettingsExpandIndicator)
|
||||
? VISIBLE : GONE);
|
||||
|
||||
mHeaderQsPanel = (QuickQSPanel) findViewById(R.id.quick_qs_panel);
|
||||
mHeaderQsPanel.setVisibility(res.getBoolean(R.bool.config_showQuickSettingsRow)
|
||||
? VISIBLE : GONE);
|
||||
|
||||
mSettingsButton = (SettingsButton) findViewById(R.id.settings_button);
|
||||
mSettingsContainer = findViewById(R.id.settings_button_container);
|
||||
@@ -151,6 +165,7 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements
|
||||
|
||||
mMultiUserSwitch = (MultiUserSwitch) findViewById(R.id.multi_user_switch);
|
||||
mMultiUserAvatar = (ImageView) mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
|
||||
mAlwaysShowMultiUserSwitch = res.getBoolean(R.bool.config_alwaysShowMultiUserSwitcher);
|
||||
|
||||
// RenderThread is doing more harm than good when touching the header (to expand quick
|
||||
// settings), so disable it for this view
|
||||
@@ -200,11 +215,8 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements
|
||||
updateSettingsAnimator();
|
||||
}
|
||||
|
||||
protected void updateSettingsAnimator() {
|
||||
mSettingsAlpha = new TouchAnimator.Builder()
|
||||
.addFloat(mEdit, "alpha", 0, 1)
|
||||
.addFloat(mMultiUserSwitch, "alpha", 0, 1)
|
||||
.build();
|
||||
private void updateSettingsAnimator() {
|
||||
mSettingsAlpha = createSettingsAlphaAnimator();
|
||||
|
||||
final boolean isRtl = isLayoutRtl();
|
||||
if (isRtl && mDateTimeGroup.getWidth() == 0) {
|
||||
@@ -221,6 +233,27 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private TouchAnimator createSettingsAlphaAnimator() {
|
||||
// If the settings icon is not shown and the user switcher is always shown, then there
|
||||
// is nothing to animate.
|
||||
if (!mShowEditIcon && mAlwaysShowMultiUserSwitch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
TouchAnimator.Builder animatorBuilder = new TouchAnimator.Builder();
|
||||
|
||||
if (mShowEditIcon) {
|
||||
animatorBuilder.addFloat(mEdit, "alpha", 0, 1);
|
||||
}
|
||||
|
||||
if (!mAlwaysShowMultiUserSwitch) {
|
||||
animatorBuilder.addFloat(mMultiUserSwitch, "alpha", 0, 1);
|
||||
}
|
||||
|
||||
return animatorBuilder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCollapsedHeight() {
|
||||
return getHeight();
|
||||
@@ -261,7 +294,10 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements
|
||||
mExpansionAmount = headerExpansionFraction;
|
||||
updateDateTimePosition();
|
||||
mAnimator.setPosition(headerExpansionFraction);
|
||||
mSettingsAlpha.setPosition(headerExpansionFraction);
|
||||
|
||||
if (mSettingsAlpha != null) {
|
||||
mSettingsAlpha.setPosition(headerExpansionFraction);
|
||||
}
|
||||
|
||||
updateAlarmVisibilities();
|
||||
|
||||
@@ -302,7 +338,7 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements
|
||||
});
|
||||
}
|
||||
|
||||
protected void updateVisibilities() {
|
||||
private void updateVisibilities() {
|
||||
updateAlarmVisibilities();
|
||||
updateDateTimePosition();
|
||||
mEmergencyOnly.setVisibility(mExpanded && (mShowEmergencyCallsOnly || mIsRoaming)
|
||||
@@ -310,9 +346,14 @@ public class QuickStatusBarHeader extends BaseStatusBarHeader implements
|
||||
mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
|
||||
TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
|
||||
final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
|
||||
mMultiUserSwitch.setVisibility(mExpanded && mMultiUserSwitch.hasMultipleUsers() && !isDemo
|
||||
|
||||
mMultiUserSwitch.setVisibility((mExpanded || mAlwaysShowMultiUserSwitch)
|
||||
&& mMultiUserSwitch.hasMultipleUsers() && !isDemo
|
||||
? View.VISIBLE : View.INVISIBLE);
|
||||
mEdit.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
|
||||
|
||||
if (mShowEditIcon) {
|
||||
mEdit.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDateTimePosition() {
|
||||
|
||||
@@ -23,13 +23,13 @@ import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.support.v4.graphics.ColorUtils;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.view.animation.PathInterpolator;
|
||||
|
||||
import com.android.keyguard.KeyguardUpdateMonitor;
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.statusbar.ExpandableNotificationRow;
|
||||
@@ -49,7 +49,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
|
||||
= new PathInterpolator(0f, 0, 0.7f, 1f);
|
||||
public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED
|
||||
= new PathInterpolator(0.3f, 0f, 0.8f, 1f);
|
||||
private static final float SCRIM_BEHIND_ALPHA = 0.62f;
|
||||
protected static final float SCRIM_BEHIND_ALPHA_KEYGUARD = 0.45f;
|
||||
protected static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f;
|
||||
private static final float SCRIM_IN_FRONT_ALPHA = 0.75f;
|
||||
@@ -66,7 +65,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
|
||||
private final View mHeadsUpScrim;
|
||||
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
|
||||
|
||||
protected float mScrimBehindAlpha = SCRIM_BEHIND_ALPHA;
|
||||
protected float mScrimBehindAlpha;
|
||||
protected float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD;
|
||||
protected float mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING;
|
||||
|
||||
@@ -109,6 +108,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
|
||||
mUnlockMethodCache = UnlockMethodCache.getInstance(context);
|
||||
mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
|
||||
mLightBarController = lightBarController;
|
||||
mScrimBehindAlpha = context.getResources().getFloat(R.dimen.scrim_behind_alpha);
|
||||
|
||||
updateHeadsUpScrim(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -948,19 +948,7 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
|
||||
inflateStatusBarWindow(context);
|
||||
mStatusBarWindow.setService(this);
|
||||
mStatusBarWindow.setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
checkUserAutohide(v, event);
|
||||
checkRemoteInputOutside(event);
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
if (mExpandedVisible) {
|
||||
animateCollapsePanels();
|
||||
}
|
||||
}
|
||||
return mStatusBarWindow.onTouchEvent(event);
|
||||
}
|
||||
});
|
||||
mStatusBarWindow.setOnTouchListener(getStatusBarWindowTouchListener());
|
||||
|
||||
mNotificationPanel = (NotificationPanelView) mStatusBarWindow.findViewById(
|
||||
R.id.notification_panel);
|
||||
@@ -1223,6 +1211,23 @@ public class StatusBar extends SystemUI implements DemoMode,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link android.view.View.OnTouchListener} that will be invoked when the
|
||||
* background window of the status bar is clicked.
|
||||
*/
|
||||
protected View.OnTouchListener getStatusBarWindowTouchListener() {
|
||||
return (v, event) -> {
|
||||
checkUserAutohide(v, event);
|
||||
checkRemoteInputOutside(event);
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
if (mExpandedVisible) {
|
||||
animateCollapsePanels();
|
||||
}
|
||||
}
|
||||
return mStatusBarWindow.onTouchEvent(event);
|
||||
};
|
||||
}
|
||||
|
||||
private void inflateShelf() {
|
||||
mNotificationShelf =
|
||||
(NotificationShelf) LayoutInflater.from(mContext).inflate(
|
||||
|
||||
@@ -27,6 +27,7 @@ import android.annotation.FloatRange;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
@@ -55,7 +56,6 @@ import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.OverScroller;
|
||||
import android.widget.ScrollView;
|
||||
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
|
||||
import com.android.systemui.ExpandHelper;
|
||||
@@ -114,6 +114,7 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
private boolean mSwipingInProgress;
|
||||
private int mCurrentStackHeight = Integer.MAX_VALUE;
|
||||
private final Paint mBackgroundPaint = new Paint();
|
||||
private boolean mShouldDrawNotificationBackground;
|
||||
|
||||
private float mExpandedHeight;
|
||||
private int mOwnScrollY;
|
||||
@@ -229,6 +230,7 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
private NotificationMenuRow mCurrIconRow;
|
||||
private View mTranslatingParentView;
|
||||
private View mGearExposedView;
|
||||
private boolean mShouldShowGear;
|
||||
|
||||
/**
|
||||
* Should in this touch motion only be scrolling allowed? It's true when the scroller was
|
||||
@@ -378,10 +380,12 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
|
||||
int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
Resources res = getResources();
|
||||
|
||||
mAmbientState = new AmbientState(context);
|
||||
mBgColor = context.getColor(R.color.notification_shade_background_color);
|
||||
int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
|
||||
int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
|
||||
int minHeight = res.getDimensionPixelSize(R.dimen.notification_min_height);
|
||||
int maxHeight = res.getDimensionPixelSize(R.dimen.notification_max_height);
|
||||
mExpandHelper = new ExpandHelper(getContext(), this,
|
||||
minHeight, maxHeight);
|
||||
mExpandHelper.setEventSource(this);
|
||||
@@ -390,14 +394,18 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
mSwipeHelper.setLongPressListener(mLongPressListener);
|
||||
mStackScrollAlgorithm = createStackScrollAlgorithm(context);
|
||||
initView(context);
|
||||
setWillNotDraw(false);
|
||||
mFalsingManager = FalsingManager.getInstance(context);
|
||||
mShouldShowGear = res.getBoolean(R.bool.config_showNotificationGear);
|
||||
mShouldDrawNotificationBackground =
|
||||
res.getBoolean(R.bool.config_drawNotificationBackground);
|
||||
|
||||
updateWillNotDraw();
|
||||
if (DEBUG) {
|
||||
mDebugPaint = new Paint();
|
||||
mDebugPaint.setColor(0xffff0000);
|
||||
mDebugPaint.setStrokeWidth(2);
|
||||
mDebugPaint.setStyle(Paint.Style.STROKE);
|
||||
}
|
||||
mFalsingManager = FalsingManager.getInstance(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -424,10 +432,11 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (mCurrentBounds.top < mCurrentBounds.bottom) {
|
||||
if (mShouldDrawNotificationBackground && mCurrentBounds.top < mCurrentBounds.bottom) {
|
||||
canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom,
|
||||
mBackgroundPaint);
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
int y = mTopPadding;
|
||||
canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
|
||||
@@ -439,6 +448,11 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
}
|
||||
|
||||
private void updateBackgroundDimming() {
|
||||
// No need to update the background color if it's not being drawn.
|
||||
if (!mShouldDrawNotificationBackground) {
|
||||
return;
|
||||
}
|
||||
|
||||
float alpha = BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount);
|
||||
alpha *= mBackgroundFadeAmount;
|
||||
// We need to manually blend in the background color
|
||||
@@ -487,6 +501,10 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
}
|
||||
|
||||
private void updateSrcDrawing() {
|
||||
if (!mShouldDrawNotificationBackground) {
|
||||
return;
|
||||
}
|
||||
|
||||
mBackgroundPaint.setXfermode(mDrawBackgroundAsSrc && !mFadingOut && !mParentNotFullyVisible
|
||||
? mSrcMode : null);
|
||||
invalidate();
|
||||
@@ -1967,9 +1985,11 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
}
|
||||
|
||||
private void updateBackground() {
|
||||
if (mAmbientState.isDark()) {
|
||||
// No need to update the background color if it's not being drawn.
|
||||
if (!mShouldDrawNotificationBackground || mAmbientState.isDark()) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateBackgroundBounds();
|
||||
if (!mCurrentBounds.equals(mBackgroundBounds)) {
|
||||
boolean animate = mAnimateNextBackgroundTop || mAnimateNextBackgroundBottom
|
||||
@@ -2119,6 +2139,12 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
}
|
||||
|
||||
private void applyCurrentBackgroundBounds() {
|
||||
// If the background of the notification is not being drawn, then there is no need to
|
||||
// exclude an area in the scrim. Rather, the scrim's color should serve as the background.
|
||||
if (!mShouldDrawNotificationBackground) {
|
||||
return;
|
||||
}
|
||||
|
||||
mScrimController.setExcludedBackgroundArea(
|
||||
mFadingOut || mParentNotFullyVisible || mAmbientState.isDark() || mIsClipped ? null
|
||||
: mCurrentBounds);
|
||||
@@ -3545,16 +3571,29 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
}
|
||||
requestChildrenUpdate();
|
||||
if (dark) {
|
||||
setWillNotDraw(!DEBUG);
|
||||
mScrimController.setExcludedBackgroundArea(null);
|
||||
} else {
|
||||
updateBackground();
|
||||
setWillNotDraw(false);
|
||||
}
|
||||
|
||||
updateWillNotDraw();
|
||||
updateContentHeight();
|
||||
notifyHeightChangeListener(mShelf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates whether or not this Layout will perform its own custom drawing (i.e. whether or
|
||||
* not {@link #onDraw(Canvas)} is called). This method should be called whenever the
|
||||
* {@link #mAmbientState}'s dark mode is toggled.
|
||||
*/
|
||||
private void updateWillNotDraw() {
|
||||
if (mAmbientState.isDark()) {
|
||||
setWillNotDraw(!DEBUG);
|
||||
} else {
|
||||
setWillNotDraw(!mShouldDrawNotificationBackground && !DEBUG);
|
||||
}
|
||||
}
|
||||
|
||||
private void setBackgroundFadeAmount(float fadeAmount) {
|
||||
mBackgroundFadeAmount = fadeAmount;
|
||||
updateBackgroundDimming();
|
||||
@@ -4106,14 +4145,14 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
* A listener that is notified when some child locations might have changed.
|
||||
*/
|
||||
public interface OnChildLocationsChangedListener {
|
||||
public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
|
||||
void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
|
||||
}
|
||||
|
||||
/**
|
||||
* A listener that is notified when the empty space below the notifications is clicked on
|
||||
*/
|
||||
public interface OnEmptySpaceClickListener {
|
||||
public void onEmptySpaceClicked(float x, float y);
|
||||
void onEmptySpaceClicked(float x, float y);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4129,7 +4168,7 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
* unrubberbanded motion to directly expand overscroll view (e.g expand
|
||||
* QS)
|
||||
*/
|
||||
public void onOverscrollTopChanged(float amount, boolean isRubberbanded);
|
||||
void onOverscrollTopChanged(float amount, boolean isRubberbanded);
|
||||
|
||||
/**
|
||||
* Notify a listener that the scroller wants to escape from the scrolling motion and
|
||||
@@ -4138,7 +4177,7 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
* @param velocity The velocity that the Scroller had when over flinging
|
||||
* @param open Should the fling open or close the overscroll view.
|
||||
*/
|
||||
public void flingTopOverscroll(float velocity, boolean open);
|
||||
void flingTopOverscroll(float velocity, boolean open);
|
||||
}
|
||||
|
||||
private class NotificationSwipeHelper extends SwipeHelper {
|
||||
@@ -4216,7 +4255,7 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
final boolean gutsExposed = (view instanceof ExpandableNotificationRow)
|
||||
&& ((ExpandableNotificationRow) view).areGutsExposed();
|
||||
|
||||
if (!isPinnedHeadsUp(view) && !gutsExposed) {
|
||||
if (mShouldShowGear && !isPinnedHeadsUp(view) && !gutsExposed) {
|
||||
// Only show the gear if we're not a heads up view and guts aren't exposed.
|
||||
checkForDrag();
|
||||
}
|
||||
@@ -4259,11 +4298,19 @@ public class NotificationStackScrollLayout extends ViewGroup
|
||||
return false; // Let SwipeHelper handle it.
|
||||
}
|
||||
|
||||
// If the gear icon should not be shown, then there is no need to check if the a swipe
|
||||
// should result in a snapping to the gear icon. As a result, just check if the swipe
|
||||
// was enough to dismiss the notification.
|
||||
if (!mShouldShowGear) {
|
||||
dismissOrSnapBack(animView, velocity, ev);
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isMenuOnLeft());
|
||||
boolean gestureFastEnough = Math.abs(velocity) > getEscapeVelocity();
|
||||
final double timeForGesture = ev.getEventTime() - ev.getDownTime();
|
||||
final boolean showGearForSlowOnGoing = !canChildBeDismissed(animView)
|
||||
&& timeForGesture >= SWIPE_GEAR_TIMING;
|
||||
&& timeForGesture >= SWIPE_GEAR_TIMING;
|
||||
|
||||
if (mGearSnappedTo && mCurrIconRow.isVisible()) {
|
||||
if (mGearSnappedOnLeft == mCurrIconRow.isMenuOnLeft()) {
|
||||
|
||||
Reference in New Issue
Block a user