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:
Anthony Chen
2016-12-01 10:58:47 -08:00
parent ab3070fd6d
commit 3cb3ad9998
17 changed files with 475 additions and 210 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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 -->

View File

@@ -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>

View File

@@ -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>

View File

@@ -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));

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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() {

View File

@@ -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);
}

View File

@@ -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(

View File

@@ -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()) {