Merge changes from topic 'nmr-plugin'

* changes:
  Add snooze to notification menu row
  Add snooze listener so that a plugin can snooze a notification
  Change NotificationGuts to contain a view
  Rename notification settings icon row / gear to notification menu row
  Create + implement plugin for NotificationSettingsIconRow
This commit is contained in:
Mady Mellor
2017-01-26 00:49:11 +00:00
committed by Android (Google) Code Review
22 changed files with 1500 additions and 813 deletions

View File

@@ -0,0 +1,74 @@
package com.android.systemui.plugins.statusbar;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.service.notification.StatusBarNotification;
import android.view.View;
import java.util.ArrayList;
import com.android.systemui.plugins.Plugin;
public interface NotificationMenuRowProvider extends Plugin {
public static final String ACTION = "com.android.systemui.action.PLUGIN_NOTIFICATION_MENU_ROW";
public static final int VERSION = 1;
/**
* Returns a list of items to populate the menu 'behind' a notification.
*/
public ArrayList<MenuItem> getMenuItems(Context context);
public interface OnMenuClickListener {
public void onMenuClicked(View row, int x, int y, MenuItem menu);
public void onMenuReset(View row);
}
public interface GutsInteractionListener {
public void onInteraction(View view);
public void closeGuts(View view);
}
public interface GutsContent {
public void setInteractionListener(GutsInteractionListener listener);
public View getContentView();
public boolean handleCloseControls();
}
public interface SnoozeGutsContent extends GutsContent {
public void setSnoozeListener(SnoozeListener listener);
public void setStatusBarNotification(StatusBarNotification sbn);
}
public interface SnoozeListener {
public void snoozeNotification(StatusBarNotification sbn, long snoozeUntil);
}
public static class MenuItem {
public Drawable icon;
public String menuDescription;
public View menuView;
public GutsContent gutsContent;
public MenuItem(Drawable i, String s, GutsContent content) {
icon = i;
menuDescription = s;
gutsContent = content;
}
public View getGutsView() {
return gutsContent.getContentView();
}
public boolean onTouch(View v, int x, int y) {
return false;
}
}
}

View File

@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"
android:fillColor="#757575"/>
<path
android:pathData="M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"
android:fillColor="#757575"/>
</vector>

View File

@@ -23,125 +23,4 @@
android:visibility="gone"
android:clickable="true"
android:gravity="top|start"
android:orientation="vertical"
android:paddingStart="@*android:dimen/notification_content_margin_start"
android:paddingEnd="8dp"
android:background="@color/notification_guts_bg_color"
android:theme="@*android:style/Theme.DeviceDefault.Light">
<!-- header -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="20dp"
android:paddingEnd="8dp"
android:paddingBottom="15dp"
android:id="@+id/notification_guts_header">
<TextView
android:id="@+id/pkgname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
style="@style/TextAppearance.NotificationGuts.Secondary" />
<TextView
android:id="@+id/channel_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_below="@id/pkgname"
style="@style/TextAppearance.NotificationGuts.Header" />
<Switch
android:id="@+id/channel_enabled_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="@null" />
</RelativeLayout>
<!-- Importance radio buttons -->
<LinearLayout
android:id="@+id/importance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioGroup
android:id="@+id/importance_buttons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingEnd="@*android:dimen/notification_content_margin_end">
<RadioButton
android:id="@+id/high_importance"
android:layout_width="wrap_content"
android:layout_height="@dimen/notification_inline_importance_height"
style="@style/TextAppearance.NotificationGuts.Radio"
android:buttonTint="@color/notification_guts_buttons" />
<RadioButton
android:id="@+id/default_importance"
android:layout_width="wrap_content"
android:layout_height="@dimen/notification_inline_importance_height"
style="@style/TextAppearance.NotificationGuts.Radio"
android:buttonTint="@color/notification_guts_buttons" />
<RadioButton
android:id="@+id/low_importance"
android:layout_width="wrap_content"
android:layout_height="@dimen/notification_inline_importance_height"
style="@style/TextAppearance.NotificationGuts.Radio"
android:buttonTint="@color/notification_guts_buttons" />
<RadioButton
android:id="@+id/min_importance"
android:layout_width="wrap_content"
android:layout_height="@dimen/notification_inline_importance_height"
style="@style/TextAppearance.NotificationGuts.Radio"
android:buttonTint="@color/notification_guts_buttons" />
</RadioGroup>
<LinearLayout
android:id="@+id/importance_buttons_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/notification_guts_importance_text"/>
<include layout="@layout/notification_guts_importance_text"/>
<include layout="@layout/notification_guts_importance_text"/>
<include layout="@layout/notification_guts_importance_text"/>
</LinearLayout>
</LinearLayout>
<!-- Channel Disabled Text -->
<TextView
android:id="@+id/channel_disabled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/notification_channel_disabled"
style="@style/TextAppearance.NotificationGuts.Secondary" />
<!-- Settings and Done buttons -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:paddingTop="16dp"
android:paddingBottom="8dp" >
<TextView
android:id="@+id/more_settings"
android:text="@string/notification_more_settings"
android:layout_width="wrap_content"
android:layout_height="36dp"
style="@style/TextAppearance.NotificationGuts.Button"
android:background="@drawable/btn_borderless_rect"
android:gravity="center"
android:paddingEnd="8dp"
android:paddingStart="8dp"
android:focusable="true" />
<TextView
android:id="@+id/done"
android:text="@string/notification_done"
android:layout_width="wrap_content"
android:layout_height="36dp"
style="@style/TextAppearance.NotificationGuts.Button"
android:background="@drawable/btn_borderless_rect"
android:gravity="center"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:focusable="true"/>
</LinearLayout>
</com.android.systemui.statusbar.NotificationGuts>
android:theme="@*android:style/Theme.DeviceDefault.Light"/>

View File

@@ -0,0 +1,146 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2017, The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.systemui.statusbar.NotificationInfo
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/notification_guts"
android:clickable="true"
android:gravity="top|start"
android:orientation="vertical"
android:paddingStart="@*android:dimen/notification_content_margin_start"
android:paddingEnd="8dp"
android:background="@color/notification_guts_bg_color"
android:theme="@*android:style/Theme.DeviceDefault.Light">
<!-- header -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="20dp"
android:paddingEnd="8dp"
android:paddingBottom="15dp"
android:id="@+id/notification_guts_header">
<TextView
android:id="@+id/pkgname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
style="@style/TextAppearance.NotificationGuts.Secondary" />
<TextView
android:id="@+id/channel_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_below="@id/pkgname"
style="@style/TextAppearance.NotificationGuts.Header" />
<Switch
android:id="@+id/channel_enabled_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="@null" />
</RelativeLayout>
<!-- Importance radio buttons -->
<LinearLayout
android:id="@+id/importance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioGroup
android:id="@+id/importance_buttons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingEnd="@*android:dimen/notification_content_margin_end">
<RadioButton
android:id="@+id/high_importance"
android:layout_width="wrap_content"
android:layout_height="@dimen/notification_inline_importance_height"
style="@style/TextAppearance.NotificationGuts.Radio"
android:buttonTint="@color/notification_guts_buttons" />
<RadioButton
android:id="@+id/default_importance"
android:layout_width="wrap_content"
android:layout_height="@dimen/notification_inline_importance_height"
style="@style/TextAppearance.NotificationGuts.Radio"
android:buttonTint="@color/notification_guts_buttons" />
<RadioButton
android:id="@+id/low_importance"
android:layout_width="wrap_content"
android:layout_height="@dimen/notification_inline_importance_height"
style="@style/TextAppearance.NotificationGuts.Radio"
android:buttonTint="@color/notification_guts_buttons" />
<RadioButton
android:id="@+id/min_importance"
android:layout_width="wrap_content"
android:layout_height="@dimen/notification_inline_importance_height"
style="@style/TextAppearance.NotificationGuts.Radio"
android:buttonTint="@color/notification_guts_buttons" />
</RadioGroup>
<LinearLayout
android:id="@+id/importance_buttons_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/notification_guts_importance_text"/>
<include layout="@layout/notification_guts_importance_text"/>
<include layout="@layout/notification_guts_importance_text"/>
<include layout="@layout/notification_guts_importance_text"/>
</LinearLayout>
</LinearLayout>
<!-- Channel Disabled Text -->
<TextView
android:id="@+id/channel_disabled"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/notification_channel_disabled"
style="@style/TextAppearance.NotificationGuts.Secondary" />
<!-- Settings and Done buttons -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:paddingTop="16dp"
android:paddingBottom="8dp" >
<TextView
android:id="@+id/more_settings"
android:text="@string/notification_more_settings"
android:layout_width="wrap_content"
android:layout_height="36dp"
style="@style/TextAppearance.NotificationGuts.Button"
android:background="@drawable/btn_borderless_rect"
android:gravity="center"
android:paddingEnd="8dp"
android:paddingStart="8dp"
android:focusable="true" />
<TextView
android:id="@+id/done"
android:text="@string/notification_done"
android:layout_width="wrap_content"
android:layout_height="36dp"
style="@style/TextAppearance.NotificationGuts.Button"
android:background="@drawable/btn_borderless_rect"
android:gravity="center"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:focusable="true"/>
</LinearLayout>
</com.android.systemui.statusbar.NotificationInfo>

View File

@@ -14,23 +14,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.systemui.statusbar.NotificationSettingsIconRow
<com.android.systemui.statusbar.NotificationMenuRow
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="invisible"
>
<com.android.systemui.statusbar.AlphaOptimizedImageView
android:id="@+id/gear_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="@dimen/notification_gear_padding"
android:src="@drawable/ic_settings"
android:tint="@color/notification_gear_color"
android:alpha="0"
android:background="?android:attr/selectableItemBackgroundBorderless"
/>
</com.android.systemui.statusbar.NotificationSettingsIconRow>
android:visibility="invisible"/>

View File

@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2017, The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.systemui.statusbar.NotificationSnooze
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/snooze_snackbar_min_height"
android:id="@+id/notification_snooze"
android:clickable="true"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="24dp"
android:paddingEnd="24dp"
android:background="@color/snooze_snackbar_bg">
<TextView
android:id="@+id/snooze_option_default"
style="@style/TextAppearance.SnoozeSnackBar"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:drawableTint="@android:color/white"
android:drawableEnd="@drawable/notification_expand_more"/>
<android.widget.Space
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1"
/>
<TextView
android:id="@+id/undo"
style="@style/TextAppearance.SnoozeSnackBar.Button"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:background="@drawable/btn_borderless_rect"
android:layout_gravity="end"
android:text="@string/snooze_undo" />
<LinearLayout
android:id="@+id/snooze_options"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:orientation="vertical"/>
</com.android.systemui.statusbar.NotificationSnooze>

View File

@@ -24,10 +24,10 @@
>
<ViewStub
android:layout="@layout/notification_settings_icon_row"
android:id="@+id/settings_icon_row_stub"
android:inflatedId="@+id/notification_settings_icon_row"
android:layout_width="wrap_content"
android:layout="@layout/notification_menu_row"
android:id="@+id/menu_row_stub"
android:inflatedId="@+id/notification_menu_row"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>

View File

@@ -103,6 +103,10 @@
<color name="notification_guts_icon_tint">#8a000000</color>
<color name="notification_guts_disabled_icon_tint">#4d000000</color>
<!-- Colors of the snooze menu reached via snooze icon behind a notification -->
<color name="snooze_snackbar_bg">#FF4A4A4A</color>
<color name="snooze_snackbar_text">#FFA6BAFF</color>
<color name="assist_orb_color">#ffffff</color>
<color name="keyguard_user_switcher_background_gradient_color">#77000000</color>

View File

@@ -106,11 +106,20 @@
<!-- Minimum layouted height of a notification in the statusbar-->
<dimen name="min_notification_layout_height">48dp</dimen>
<!-- Width of the space containing the gear icon behind a notification -->
<dimen name="notification_gear_width">64dp</dimen>
<!-- Size of the space to place a notification menu item -->
<dimen name="notification_menu_icon_size">64dp</dimen>
<!-- The space around the gear icon displayed behind a notification -->
<dimen name="notification_gear_padding">20dp</dimen>
<!-- The space around a notification menu item -->
<dimen name="notification_menu_icon_padding">20dp</dimen>
<!-- The minimum height for the snackbar shown after the snooze option has been chosen. -->
<dimen name="snooze_snackbar_min_height">48dp</dimen>
<!-- The text size of options in the snooze menu. -->
<dimen name="snooze_option_text_size">14sp</dimen>
<!-- The padding around options int the snooze menu. -->
<dimen name="snooze_option_padding">8dp</dimen>
<!-- size at which Notification icons will be drawn in the status bar -->
<dimen name="status_bar_icon_drawing_size">17dp</dimen>

View File

@@ -1375,8 +1375,26 @@
<!-- Notification: Control panel: Label for button that dismisses control panel. [CHAR LIMIT=NONE] -->
<string name="notification_done">Done</string>
<!-- Notification: Gear: Content description for the gear. [CHAR LIMIT=NONE] -->
<string name="notification_gear_accessibility"><xliff:g id="app_name" example="YouTube">%1$s</xliff:g> notification controls</string>
<!-- Notification: Menu row: Content description for menu items. [CHAR LIMIT=NONE] -->
<string name="notification_menu_accessibility"><xliff:g id="app_name" example="YouTube">%1$s</xliff:g> <xliff:g id="menu_description" example="notification controls">%2$s</xliff:g></string>
<!-- Notification: Menu row: Content description for the gear menu item. [CHAR LIMIT=NONE] -->
<string name="notification_menu_gear_description">notification controls</string>
<!-- Notification: Menu row: Content description for the snooze icon. [CHAR LIMIT=NONE] -->
<string name="notification_menu_snooze_description">notification snooze options</string>
<!-- Notification: Menu row: Snooze options: 15 minute option. [CHAR LIMIT=50]-->
<string name="snooze_option_15_min">15 minutes</string>
<!-- Notification: Menu row: Snooze options: 30 minute option. [CHAR LIMIT=50]-->
<string name="snooze_option_30_min">30 minutes</string>
<!-- Notification: Menu row: Snooze options: 1 hour option. [CHAR LIMIT=50]-->
<string name="snooze_option_1_hour">1 hour</string>
<!-- Notification: Menu row: Snooze undo button label. [CHAR LIMIT=50]-->
<string name="snooze_undo">UNDO</string>
<!-- Notification: Menu row: Snooze: message indicating how long the notification was snoozed for. [CHAR LIMIT=100]-->
<string name="snoozed_for_time">Snoozed for <xliff:g id="time_amount" example="15 minutes">%1$s</xliff:g></string>
<!-- Title of the battery settings detail panel [CHAR LIMIT=20] -->
<string name="battery_panel_title">Battery usage</string>

View File

@@ -383,6 +383,20 @@
<item name="android:textColor">?android:attr/colorAccent</item>
</style>
<style name="TextAppearance.SnoozeSnackBar">
<item name="android:textSize">14sp</item>
<item name="android:fontFamily">roboto-regular</item>
<item name="android:textColor">@android:color/white</item>
</style>
<style name="TextAppearance.SnoozeSnackBar.Button">
<item name="android:textSize">14sp</item>
<item name="android:textAllCaps">true</item>
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:gravity">center</item>
<item name="android:textColor">@color/snooze_snackbar_text</item>
</style>
<style name="edit_theme" parent="@*android:style/Theme.DeviceDefault.QuickSettings">
<item name="android:colorBackground">?android:attr/colorSecondary</item>
</style>

View File

@@ -32,7 +32,9 @@ import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.NotificationMenuRow;
import java.util.HashMap;
@@ -85,10 +87,12 @@ public class SwipeHelper implements Gefingerpoken {
private int mFalsingThreshold;
private boolean mTouchAboveFalsingThreshold;
private boolean mDisableHwLayers;
private Context mContext;
private HashMap<View, Animator> mDismissPendingMap = new HashMap<>();
public SwipeHelper(int swipeDirection, Callback callback, Context context) {
mContext = context;
mCallback = callback;
mHandler = new Handler();
mSwipeDirection = swipeDirection;
@@ -249,6 +253,7 @@ public class SwipeHelper implements Gefingerpoken {
}
}
@Override
public boolean onInterceptTouchEvent(final MotionEvent ev) {
final int action = ev.getAction();
@@ -280,7 +285,8 @@ public class SwipeHelper implements Gefingerpoken {
mCurrView.getLocationOnScreen(mTmpPos);
final int x = (int) ev.getRawX() - mTmpPos[0];
final int y = (int) ev.getRawY() - mTmpPos[1];
mLongPressListener.onLongPress(mCurrView, x, y);
mLongPressListener.onLongPress(mCurrView, x, y,
NotificationMenuRow.getLongpressMenuItem(mContext));
}
}
};
@@ -379,6 +385,7 @@ public class SwipeHelper implements Gefingerpoken {
animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
}
AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed);
}
@@ -401,10 +408,12 @@ public class SwipeHelper implements Gefingerpoken {
anim.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
@Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;
}
@Override
public void onAnimationEnd(Animator animation) {
updateSwipeProgressFromOffset(animView, canBeDismissed);
mDismissPendingMap.remove(animView);
@@ -435,6 +444,7 @@ public class SwipeHelper implements Gefingerpoken {
public void snapChild(final View animView, final float targetLeft, float velocity) {
final boolean canBeDismissed = mCallback.canChildBeDismissed(animView);
AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
onTranslationUpdate(animView, (float) animation.getAnimatedValue(), canBeDismissed);
}
@@ -447,6 +457,7 @@ public class SwipeHelper implements Gefingerpoken {
int duration = SNAP_ANIM_LEN;
anim.setDuration(duration);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animator) {
mSnappingChild = false;
updateSwipeProgressFromOffset(animView, canBeDismissed);
@@ -522,6 +533,7 @@ public class SwipeHelper implements Gefingerpoken {
}
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mLongPressSent) {
return true;
@@ -690,6 +702,6 @@ public class SwipeHelper implements Gefingerpoken {
* Equivalent to {@link View.OnLongClickListener#onLongClick(View)} with coordinates
* @return whether the longpress was handled
*/
boolean onLongPress(View v, int x, int y);
boolean onLongPress(View v, int x, int y, MenuItem item);
}
}

View File

@@ -101,6 +101,10 @@ import com.android.systemui.RecentsComponent;
import com.android.systemui.SwipeHelper;
import com.android.systemui.SystemUI;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeGutsContent;
import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeListener;
import com.android.systemui.recents.Recents;
import com.android.systemui.statusbar.NotificationData.Entry;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
@@ -247,6 +251,7 @@ public abstract class BaseStatusBar extends SystemUI implements
// which notification is currently being longpress-examined by the user
private NotificationGuts mNotificationGutsExposed;
private MenuItem mGutsMenuItem;
private KeyboardShortcuts mKeyboardShortcuts;
@@ -702,6 +707,7 @@ public abstract class BaseStatusBar extends SystemUI implements
}
}
@Override
public void start() {
mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
@@ -967,7 +973,7 @@ public abstract class BaseStatusBar extends SystemUI implements
entry.row.reInflateViews();
if (exposedGuts) {
mNotificationGutsExposed = entry.row.getGuts();
bindGuts(entry.row);
bindGuts(entry.row, mGutsMenuItem);
}
inflateViews(entry, mStackScroller);
}
@@ -1032,6 +1038,7 @@ public abstract class BaseStatusBar extends SystemUI implements
@Override
public boolean onDismiss() {
AsyncTask.execute(new Runnable() {
@Override
public void run() {
TaskStackBuilder.create(mContext)
.addNextIntentWithParentStack(intent)
@@ -1045,11 +1052,18 @@ public abstract class BaseStatusBar extends SystemUI implements
}, false /* afterKeyguardGone */);
}
private void bindGuts(final ExpandableNotificationRow row) {
protected void setNotificationSnoozed(StatusBarNotification sbn, long snoozeUntil) {
mNotificationListener.snoozeNotification(sbn.getKey(), snoozeUntil);
}
public SnoozeListener getSnoozeListener() {
return null;
}
private void bindGuts(final ExpandableNotificationRow row, MenuItem item) {
row.inflateGuts();
row.setGutsView(item);
final StatusBarNotification sbn = row.getStatusBarNotification();
final NotificationChannel channel = row.getEntry().channel;
PackageManager pmUser = getPackageManagerForUser(mContext, sbn.getUser().getIdentifier());
row.setTag(sbn.getPackageName());
final NotificationGuts guts = row.getGuts();
guts.setClosedListener((NotificationGuts g) -> {
@@ -1059,43 +1073,53 @@ public abstract class BaseStatusBar extends SystemUI implements
mNotificationGutsExposed = null;
});
final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
if (item.gutsContent instanceof SnoozeGutsContent) {
((SnoozeGutsContent) item.gutsContent).setSnoozeListener(getSnoozeListener());
((SnoozeGutsContent) item.gutsContent).setStatusBarNotification(sbn);
}
final String pkg = sbn.getPackageName();
final NotificationGuts.OnSettingsClickListener onSettingsClick =
(View v, int appUid) -> {
MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTE_INFO);
guts.resetFalsingCheck();
startAppNotificationSettingsActivity(pkg, appUid);
};
final View.OnClickListener onDoneClick =
(View v) -> {
// If the user has security enabled, show challenge if the setting is changed.
if (guts.hasImportanceChanged()
&& isLockscreenPublicMode(sbn.getUser().getIdentifier())
&& (mState == StatusBarState.KEYGUARD
|| mState == StatusBarState.SHADE_LOCKED)) {
OnDismissAction dismissAction = new OnDismissAction() {
@Override
public boolean onDismiss() {
closeControls(row, guts, v);
return true;
}
};
onLockedNotificationImportanceChange(dismissAction);
} else {
closeControls(row, guts, v);
}
};
guts.bindNotification(pmUser, iNotificationManager, sbn, channel,
onSettingsClick, onDoneClick, mNonBlockablePkgs);
if (item.gutsContent instanceof NotificationInfo) {
final NotificationChannel channel = row.getEntry().channel;
PackageManager pmUser = getPackageManagerForUser(mContext,
sbn.getUser().getIdentifier());
final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
final String pkg = sbn.getPackageName();
NotificationInfo info = (NotificationInfo) item.gutsContent;
final NotificationInfo.OnSettingsClickListener onSettingsClick = (View v,
int appUid) -> {
MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTE_INFO);
guts.resetFalsingCheck();
startAppNotificationSettingsActivity(pkg, appUid);
};
final View.OnClickListener onDoneClick = (View v) -> {
// If the user has security enabled, show challenge if the setting is changed.
if (info.hasImportanceChanged()
&& isLockscreenPublicMode(sbn.getUser().getIdentifier())
&& (mState == StatusBarState.KEYGUARD
|| mState == StatusBarState.SHADE_LOCKED)) {
OnDismissAction dismissAction = new OnDismissAction() {
@Override
public boolean onDismiss() {
saveAndCloseNotificationMenu(info, row, guts, v);
return true;
}
};
onLockedNotificationImportanceChange(dismissAction);
} else {
saveAndCloseNotificationMenu(info, row, guts, v);
}
};
info.bindNotification(pmUser, iNotificationManager, sbn, channel, onSettingsClick,
onDoneClick,
mNonBlockablePkgs);
}
}
private void closeControls(
private void saveAndCloseNotificationMenu(NotificationInfo info,
ExpandableNotificationRow row, NotificationGuts guts, View done) {
guts.resetFalsingCheck();
info.saveImportance();
int[] rowLocation = new int[2];
int[] doneLocation = new int[2];
row.getLocationOnScreen(rowLocation);
@@ -1111,7 +1135,8 @@ public abstract class BaseStatusBar extends SystemUI implements
protected SwipeHelper.LongPressListener getNotificationLongClicker() {
return new SwipeHelper.LongPressListener() {
@Override
public boolean onLongPress(View v, final int x, final int y) {
public boolean onLongPress(View v, final int x, final int y,
MenuItem item) {
if (!(v instanceof ExpandableNotificationRow)) {
return false;
}
@@ -1121,10 +1146,10 @@ public abstract class BaseStatusBar extends SystemUI implements
}
final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
bindGuts(row);
bindGuts(row, item);
NotificationGuts guts = row.getGuts();
// Assume we are a status_bar_notification_row
final NotificationGuts guts = row.getGuts();
if (guts == null) {
// This view has no guts. Examples are the more card or the dismiss all view
return false;
@@ -1142,6 +1167,7 @@ public abstract class BaseStatusBar extends SystemUI implements
guts.setVisibility(View.INVISIBLE);
// Post to ensure the the guts are properly laid out.
guts.post(new Runnable() {
@Override
public void run() {
if (row.getWindowToken() == null) {
Log.e(TAG, "Trying to show notification guts, but not attached to "
@@ -1172,6 +1198,7 @@ public abstract class BaseStatusBar extends SystemUI implements
row.closeRemoteInput();
mStackScroller.onHeightChanged(row, true /* needsAnimation */);
mNotificationGutsExposed = guts;
mGutsMenuItem = item;
}
});
return true;
@@ -1483,6 +1510,7 @@ public abstract class BaseStatusBar extends SystemUI implements
}
protected class H extends Handler {
@Override
public void handleMessage(Message m) {
switch (m.what) {
case MSG_SHOW_RECENT_APPS:
@@ -1754,6 +1782,7 @@ public abstract class BaseStatusBar extends SystemUI implements
&& PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
mCurrentUserId);
dismissKeyguardThenExecute(new OnDismissAction() {
@Override
public boolean onDismiss() {
new Thread() {
@Override
@@ -1804,6 +1833,7 @@ public abstract class BaseStatusBar extends SystemUI implements
private final class NotificationClicker implements View.OnClickListener {
private final int[] mTmpInt2 = new int[2];
@Override
public void onClick(final View v) {
if (!(v instanceof ExpandableNotificationRow)) {
Log.e(TAG, "NotificationClicker called on a view that is not a notification row.");
@@ -1847,6 +1877,7 @@ public abstract class BaseStatusBar extends SystemUI implements
&& PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
mCurrentUserId);
dismissKeyguardThenExecute(new OnDismissAction() {
@Override
public boolean onDismiss() {
if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
// Release the HUN notification to the shade.

View File

@@ -51,6 +51,7 @@ import com.android.internal.widget.CachingIconView;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
import com.android.systemui.statusbar.notification.HybridNotificationView;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
@@ -120,7 +121,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
private int mNotificationColor;
private ExpansionLogger mLogger;
private String mLoggingKey;
private NotificationSettingsIconRow mSettingsIconRow;
private NotificationMenuRow mMenuRow;
private NotificationGuts mGuts;
private NotificationData.Entry mEntry;
private StatusBarNotification mStatusBarNotification;
@@ -132,7 +133,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
private boolean mChildrenExpanded;
private boolean mIsSummaryWithChildren;
private NotificationChildrenContainer mChildrenContainer;
private ViewStub mSettingsIconRowStub;
private ViewStub mMenuRowStub;
private ViewStub mGutsStub;
private boolean mIsSystemChildExpanded;
private boolean mIsPinned;
@@ -205,6 +206,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
private Runnable mOnDismissRunnable;
private boolean mIsLowPriority;
@Override
public boolean isGroupExpansionChanging() {
if (isChildInGroup()) {
return mNotificationParent.isGroupExpansionChanging();
@@ -386,8 +388,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
public void setAppName(String appName) {
mAppName = appName;
if (mSettingsIconRow != null) {
mSettingsIconRow.setAppName(mAppName);
if (mMenuRow != null) {
mMenuRow.setAppName(mAppName);
}
}
@@ -418,6 +420,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
row.setIsChildInGroup(false, null);
}
@Override
public boolean isChildInGroup() {
return mNotificationParent != null;
}
@@ -455,7 +458,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
@Override
protected boolean handleSlideBack() {
if (mSettingsIconRow != null && mSettingsIconRow.isVisible()) {
if (mMenuRow != null && mMenuRow.isVisible()) {
animateTranslateNotification(0 /* targetLeft */);
return true;
}
@@ -679,6 +682,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
mHeadsUpManager = headsUpManager;
}
public void setGutsView(MenuItem item) {
if (mGuts != null) {
item.gutsContent.setInteractionListener(mGuts);
mGuts.setGutsContent(item.gutsContent);
}
}
public void reInflateViews() {
initDimens();
if (mIsSummaryWithChildren) {
@@ -695,16 +705,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
mGuts.setVisibility(oldGuts.getVisibility());
addView(mGuts, index);
}
if (mSettingsIconRow != null) {
View oldSettings = mSettingsIconRow;
int settingsIndex = indexOfChild(oldSettings);
removeView(oldSettings);
mSettingsIconRow = (NotificationSettingsIconRow) LayoutInflater.from(mContext).inflate(
R.layout.notification_settings_icon_row, this, false);
mSettingsIconRow.setNotificationRowParent(ExpandableNotificationRow.this);
mSettingsIconRow.setAppName(mAppName);
mSettingsIconRow.setVisibility(oldSettings.getVisibility());
addView(mSettingsIconRow, settingsIndex);
if (mMenuRow != null) {
View oldMenu = mMenuRow;
int menuIndex = indexOfChild(oldMenu);
removeView(oldMenu);
mMenuRow = (NotificationMenuRow) LayoutInflater.from(mContext).inflate(
R.layout.notification_menu_row, this, false);
mMenuRow.setNotificationRowParent(ExpandableNotificationRow.this);
mMenuRow.setAppName(mAppName);
mMenuRow.setVisibility(oldMenu.getVisibility());
addView(mMenuRow, menuIndex);
}
for (NotificationContentView l : mLayouts) {
@@ -1033,21 +1043,19 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
super.onFinishInflate();
mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
mLayouts = new NotificationContentView[] {mPrivateLayout, mPublicLayout};
for (NotificationContentView l : mLayouts) {
l.setExpandClickListener(mExpandClickListener);
l.setContainingNotification(this);
}
mSettingsIconRowStub = (ViewStub) findViewById(R.id.settings_icon_row_stub);
mSettingsIconRowStub.setOnInflateListener(new ViewStub.OnInflateListener() {
mMenuRowStub = (ViewStub) findViewById(R.id.menu_row_stub);
mMenuRowStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
mSettingsIconRow = (NotificationSettingsIconRow) inflated;
mSettingsIconRow.setNotificationRowParent(ExpandableNotificationRow.this);
mSettingsIconRow.setAppName(mAppName);
mMenuRow = (NotificationMenuRow) inflated;
mMenuRow.setNotificationRowParent(ExpandableNotificationRow.this);
mMenuRow.setAppName(mAppName);
}
});
mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
@@ -1079,7 +1087,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
mTranslateableViews.add(getChildAt(i));
}
// Remove views that don't translate
mTranslateableViews.remove(mSettingsIconRowStub);
mTranslateableViews.remove(mMenuRowStub);
mTranslateableViews.remove(mChildrenContainerStub);
mTranslateableViews.remove(mGutsStub);
}
@@ -1094,8 +1102,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
}
}
invalidateOutline();
if (mSettingsIconRow != null) {
mSettingsIconRow.resetState();
if (mMenuRow != null) {
mMenuRow.resetState(true /* notify */);
}
}
@@ -1122,8 +1130,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
}
}
invalidateOutline();
if (mSettingsIconRow != null) {
mSettingsIconRow.updateSettingsIcons(translationX, getMeasuredWidth());
if (mMenuRow != null) {
mMenuRow.updateMenuAlpha(translationX, getMeasuredWidth());
}
}
@@ -1160,8 +1168,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
@Override
public void onAnimationEnd(Animator anim) {
if (!cancelled && mSettingsIconRow != null && leftTarget == 0) {
mSettingsIconRow.resetState();
if (!cancelled && mMenuRow != null && leftTarget == 0) {
mMenuRow.resetState(true /* notify */);
mTranslateAnim = null;
}
}
@@ -1171,17 +1179,17 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
}
public float getSpaceForGear() {
if (mSettingsIconRow != null) {
return mSettingsIconRow.getSpaceForGear();
if (mMenuRow != null) {
return mMenuRow.getSpaceForMenu();
}
return 0;
}
public NotificationSettingsIconRow getSettingsRow() {
if (mSettingsIconRow == null) {
mSettingsIconRowStub.inflate();
public NotificationMenuRow getSettingsRow() {
if (mMenuRow == null) {
mMenuRowStub.inflate();
}
return mSettingsIconRow;
return mMenuRow;
}
public void inflateGuts() {
@@ -1404,6 +1412,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
}
}
@Override
public boolean isGroupExpanded() {
return mGroupManager.isGroupExpanded(mStatusBarNotification);
}
@@ -1457,8 +1466,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
updateMaxHeights();
if (mSettingsIconRow != null) {
mSettingsIconRow.updateVerticalLocation();
if (mMenuRow != null) {
mMenuRow.updateVerticalLocation();
}
updateContentShiftHeight();
}
@@ -1505,6 +1514,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
mSensitiveHiddenInGeneral = hideSensitive;
}
@Override
public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
mHideSensitiveForIntrinsicHeight = hideSensitive;
if (mIsSummaryWithChildren) {
@@ -1517,6 +1527,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
}
}
@Override
public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
long duration) {
boolean oldShowingPublic = mShowingPublic;
@@ -1581,6 +1592,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
}
}
@Override
public boolean mustStayOnScreen() {
return mIsHeadsUp;
}

View File

@@ -40,6 +40,7 @@ import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RadioButton;
@@ -53,6 +54,8 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.Utils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsContent;
import com.android.systemui.statusbar.stack.StackStateAnimator;
import java.util.Set;
@@ -60,7 +63,8 @@ import java.util.Set;
/**
* The guts of a notification revealed when performing a long press.
*/
public class NotificationGuts extends LinearLayout {
public class NotificationGuts extends FrameLayout
implements NotificationMenuRowProvider.GutsInteractionListener {
private static final String TAG = "NotificationGuts";
private static final long CLOSE_GUTS_DELAY = 8000;
@@ -69,24 +73,14 @@ public class NotificationGuts extends LinearLayout {
private int mClipBottomAmount;
private int mActualHeight;
private boolean mExposed;
private INotificationManager mINotificationManager;
private int mStartingUserImportance;
private StatusBarNotification mStatusBarNotification;
private NotificationChannel mNotificationChannel;
private View mImportanceGroup;
private View mChannelDisabled;
private Switch mChannelEnabledSwitch;
private RadioButton mMinImportanceButton;
private RadioButton mLowImportanceButton;
private RadioButton mDefaultImportanceButton;
private RadioButton mHighImportanceButton;
private Handler mHandler;
private Runnable mFalsingCheck;
private boolean mNeedsFalsingProtection;
private OnGutsClosedListener mListener;
private GutsContent mGutsContent;
public interface OnGutsClosedListener {
public void onGutsClosed(NotificationGuts guts);
}
@@ -103,11 +97,22 @@ public class NotificationGuts extends LinearLayout {
}
}
};
final TypedArray ta =
context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Theme, 0, 0);
final TypedArray ta = context.obtainStyledAttributes(attrs,
com.android.internal.R.styleable.Theme, 0, 0);
ta.recycle();
}
public NotificationGuts(Context context) {
this(context, null);
}
public void setGutsContent(GutsContent content) {
mGutsContent = content;
removeAllViews();
addView(mGutsContent.getContentView());
}
public void resetFalsingCheck() {
mHandler.removeCallbacks(mFalsingCheck);
if (mNeedsFalsingProtection && mExposed) {
@@ -165,213 +170,23 @@ public class NotificationGuts extends LinearLayout {
void onClick(View v, int appUid);
}
void bindNotification(final PackageManager pm, final INotificationManager iNotificationManager,
final StatusBarNotification sbn, final NotificationChannel channel,
OnSettingsClickListener onSettingsClick,
OnClickListener onDoneClick, final Set<String> nonBlockablePkgs) {
mINotificationManager = iNotificationManager;
mNotificationChannel = channel;
mStatusBarNotification = sbn;
mStartingUserImportance = channel.getImportance();
final String pkg = sbn.getPackageName();
int appUid = -1;
String appname = pkg;
Drawable pkgicon = null;
try {
final ApplicationInfo info = pm.getApplicationInfo(pkg,
PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_DIRECT_BOOT_AWARE);
if (info != null) {
appUid = info.uid;
appname = String.valueOf(pm.getApplicationLabel(info));
pkgicon = pm.getApplicationIcon(info);
}
} catch (PackageManager.NameNotFoundException e) {
// app is gone, just show package name and generic icon
pkgicon = pm.getDefaultActivityIcon();
}
// If this is the placeholder channel, don't use our channel-specific text.
String appNameText;
CharSequence channelNameText;
if (channel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
appNameText = appname;
channelNameText = mContext.getString(R.string.notification_header_default_channel);
} else {
appNameText = mContext.getString(R.string.notification_importance_header_app, appname);
channelNameText = channel.getName();
}
((TextView) findViewById(R.id.pkgname)).setText(appNameText);
((TextView) findViewById(R.id.channel_name)).setText(channelNameText);
// Settings button.
final TextView settingsButton = (TextView) findViewById(R.id.more_settings);
if (appUid >= 0 && onSettingsClick != null) {
final int appUidF = appUid;
settingsButton.setOnClickListener(
(View view) -> { onSettingsClick.onClick(view, appUidF); });
settingsButton.setText(R.string.notification_more_settings);
} else {
settingsButton.setVisibility(View.GONE);
}
// Done button.
final TextView doneButton = (TextView) findViewById(R.id.done);
doneButton.setText(R.string.notification_done);
doneButton.setOnClickListener(onDoneClick);
boolean nonBlockable = false;
try {
final PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
nonBlockable = Utils.isSystemPackage(getResources(), pm, info);
} catch (PackageManager.NameNotFoundException e) {
// unlikely.
}
if (nonBlockablePkgs != null) {
nonBlockable |= nonBlockablePkgs.contains(pkg);
}
final View importanceButtons = findViewById(R.id.importance_buttons);
bindToggles(importanceButtons, mStartingUserImportance, nonBlockable);
// Importance Text (hardcoded to 4 importance levels)
final ViewGroup importanceTextGroup =
(ViewGroup) findViewById(R.id.importance_buttons_text);
final int size = importanceTextGroup.getChildCount();
for (int i = 0; i < size; i++) {
int importanceNameResId = 0;
int importanceDescResId = 0;
switch (i) {
case 0:
importanceNameResId = R.string.high_importance;
importanceDescResId = R.string.notification_importance_high;
break;
case 1:
importanceNameResId = R.string.default_importance;
importanceDescResId = R.string.notification_importance_default;
break;
case 2:
importanceNameResId = R.string.low_importance;
importanceDescResId = R.string.notification_importance_low;
break;
case 3:
importanceNameResId = R.string.min_importance;
importanceDescResId = R.string.notification_importance_min;
break;
default:
Log.e(TAG, "Too many importance groups in this layout.");
break;
}
final ViewGroup importanceChildGroup = (ViewGroup) importanceTextGroup.getChildAt(i);
((TextView) importanceChildGroup.getChildAt(0)).setText(importanceNameResId);
((TextView) importanceChildGroup.getChildAt(1)).setText(importanceDescResId);
}
// Top-level importance group
mImportanceGroup = findViewById(R.id.importance);
mChannelDisabled = findViewById(R.id.channel_disabled);
updateImportanceGroup();
}
public boolean hasImportanceChanged() {
return mStartingUserImportance != getSelectedImportance();
}
private void saveImportance() {
int selectedImportance = getSelectedImportance();
if (selectedImportance == mStartingUserImportance) {
return;
}
MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
selectedImportance - mStartingUserImportance);
mNotificationChannel.setImportance(selectedImportance);
try {
mINotificationManager.updateNotificationChannelForPackage(
mStatusBarNotification.getPackageName(), mStatusBarNotification.getUid(),
mNotificationChannel);
} catch (RemoteException e) {
// :(
}
}
private int getSelectedImportance() {
if (!mChannelEnabledSwitch.isChecked()) {
return NotificationManager.IMPORTANCE_NONE;
} else if (mMinImportanceButton.isChecked()) {
return NotificationManager.IMPORTANCE_MIN;
} else if (mLowImportanceButton.isChecked()) {
return NotificationManager.IMPORTANCE_LOW;
} else if (mDefaultImportanceButton.isChecked()) {
return NotificationManager.IMPORTANCE_DEFAULT;
} else if (mHighImportanceButton.isChecked()) {
return NotificationManager.IMPORTANCE_HIGH;
} else {
return NotificationManager.IMPORTANCE_UNSPECIFIED;
}
}
private void bindToggles(final View importanceButtons, final int importance,
final boolean nonBlockable) {
// Enabled Switch
mChannelEnabledSwitch = (Switch) findViewById(R.id.channel_enabled_switch);
mChannelEnabledSwitch.setChecked(importance != NotificationManager.IMPORTANCE_NONE);
mChannelEnabledSwitch.setVisibility(nonBlockable ? View.INVISIBLE : View.VISIBLE);
// Importance Buttons
mMinImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.min_importance);
mLowImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.low_importance);
mDefaultImportanceButton =
(RadioButton) importanceButtons.findViewById(R.id.default_importance);
mHighImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.high_importance);
// Set to current importance setting
switch (importance) {
case NotificationManager.IMPORTANCE_UNSPECIFIED:
case NotificationManager.IMPORTANCE_NONE:
break;
case NotificationManager.IMPORTANCE_MIN:
mMinImportanceButton.setChecked(true);
break;
case NotificationManager.IMPORTANCE_LOW:
mLowImportanceButton.setChecked(true);
break;
case NotificationManager.IMPORTANCE_DEFAULT:
mDefaultImportanceButton.setChecked(true);
break;
case NotificationManager.IMPORTANCE_HIGH:
case NotificationManager.IMPORTANCE_MAX:
mHighImportanceButton.setChecked(true);
break;
}
// Callback when checked.
mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
resetFalsingCheck();
updateImportanceGroup();
});
((RadioGroup) importanceButtons).setOnCheckedChangeListener(
(buttonView, isChecked) -> { resetFalsingCheck(); });
}
private void updateImportanceGroup() {
final boolean disabled = getSelectedImportance() == NotificationManager.IMPORTANCE_NONE;
mImportanceGroup.setVisibility(disabled ? View.GONE : View.VISIBLE);
mChannelDisabled.setVisibility(disabled ? View.VISIBLE : View.GONE);
}
public void closeControls(int x, int y, boolean saveImportance) {
if (saveImportance) {
saveImportance();
}
if (getWindowToken() == null) {
if (mListener != null) {
mListener.onGutsClosed(this);
}
return;
}
if (mGutsContent == null || !mGutsContent.handleCloseControls()) {
animateClose(x, y);
}
setExposed(false, mNeedsFalsingProtection);
if (mListener != null) {
mListener.onGutsClosed(this);
}
}
private void animateClose(int x, int y) {
if (x == -1 || y == -1) {
x = (getLeft() + getRight()) / 2;
y = (getTop() + getHeight() / 2);
@@ -391,10 +206,6 @@ public class NotificationGuts extends LinearLayout {
}
});
a.start();
setExposed(false, mNeedsFalsingProtection);
if (mListener != null) {
mListener.onGutsClosed(this);
}
}
public void setActualHeight(int actualHeight) {
@@ -439,4 +250,14 @@ public class NotificationGuts extends LinearLayout {
public boolean isExposed() {
return mExposed;
}
@Override
public void onInteraction(View view) {
resetFalsingCheck();
}
@Override
public void closeGuts(View view) {
closeControls(-1 /* x */, -1 /* y */, true /* notify */);
}
}

View File

@@ -0,0 +1,319 @@
package com.android.systemui.statusbar;
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.app.INotificationManager;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.SeekBar;
import android.widget.Switch;
import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.Utils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsContent;
import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsInteractionListener;
import com.android.systemui.statusbar.NotificationGuts.OnSettingsClickListener;
import com.android.systemui.statusbar.stack.StackStateAnimator;
import java.util.Set;
/**
* The guts of a notification revealed when performing a long press.
*/
public class NotificationInfo extends LinearLayout implements GutsContent {
private static final String TAG = "InfoGuts";
private INotificationManager mINotificationManager;
private int mStartingUserImportance;
private StatusBarNotification mStatusBarNotification;
private NotificationChannel mNotificationChannel;
private ImageView mAutoButton;
private TextView mImportanceSummary;
private TextView mImportanceTitle;
private boolean mAuto;
private View mImportanceGroup;
private View mChannelDisabled;
private Switch mChannelEnabledSwitch;
private RadioButton mMinImportanceButton;
private RadioButton mLowImportanceButton;
private RadioButton mDefaultImportanceButton;
private RadioButton mHighImportanceButton;
private GutsInteractionListener mGutsInteractionListener;
public NotificationInfo(Context context, AttributeSet attrs) {
super(context, attrs);
}
interface OnSettingsClickListener {
void onClick(View v, int appUid);
}
void bindNotification(final PackageManager pm, final INotificationManager iNotificationManager,
final StatusBarNotification sbn, final NotificationChannel channel,
OnSettingsClickListener onSettingsClick,
OnClickListener onDoneClick, final Set<String> nonBlockablePkgs) {
mINotificationManager = iNotificationManager;
mNotificationChannel = channel;
mStatusBarNotification = sbn;
mStartingUserImportance = channel.getImportance();
final String pkg = sbn.getPackageName();
int appUid = -1;
String appname = pkg;
Drawable pkgicon = null;
try {
final ApplicationInfo info = pm.getApplicationInfo(pkg,
PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.MATCH_DISABLED_COMPONENTS
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_DIRECT_BOOT_AWARE);
if (info != null) {
appUid = info.uid;
appname = String.valueOf(pm.getApplicationLabel(info));
pkgicon = pm.getApplicationIcon(info);
}
} catch (PackageManager.NameNotFoundException e) {
// app is gone, just show package name and generic icon
pkgicon = pm.getDefaultActivityIcon();
}
// If this is the placeholder channel, don't use our channel-specific text.
String appNameText;
CharSequence channelNameText;
if (channel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) {
appNameText = appname;
channelNameText = mContext.getString(R.string.notification_header_default_channel);
} else {
appNameText = mContext.getString(R.string.notification_importance_header_app, appname);
channelNameText = channel.getName();
}
((TextView) findViewById(R.id.pkgname)).setText(appNameText);
((TextView) findViewById(R.id.channel_name)).setText(channelNameText);
// Settings button.
final TextView settingsButton = (TextView) findViewById(R.id.more_settings);
if (appUid >= 0 && onSettingsClick != null) {
final int appUidF = appUid;
settingsButton.setOnClickListener(
(View view) -> {
onSettingsClick.onClick(view, appUidF);
});
settingsButton.setText(R.string.notification_more_settings);
} else {
settingsButton.setVisibility(View.GONE);
}
// Done button.
final TextView doneButton = (TextView) findViewById(R.id.done);
doneButton.setText(R.string.notification_done);
doneButton.setOnClickListener(onDoneClick);
boolean nonBlockable = false;
try {
final PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
nonBlockable = Utils.isSystemPackage(getResources(), pm, info);
} catch (PackageManager.NameNotFoundException e) {
// unlikely.
}
if (nonBlockablePkgs != null) {
nonBlockable |= nonBlockablePkgs.contains(pkg);
}
final View importanceButtons = findViewById(R.id.importance_buttons);
bindToggles(importanceButtons, mStartingUserImportance, nonBlockable);
// Importance Text (hardcoded to 4 importance levels)
final ViewGroup importanceTextGroup = (ViewGroup) findViewById(
R.id.importance_buttons_text);
final int size = importanceTextGroup.getChildCount();
for (int i = 0; i < size; i++) {
int importanceNameResId = 0;
int importanceDescResId = 0;
switch (i) {
case 0:
importanceNameResId = R.string.high_importance;
importanceDescResId = R.string.notification_importance_high;
break;
case 1:
importanceNameResId = R.string.default_importance;
importanceDescResId = R.string.notification_importance_default;
break;
case 2:
importanceNameResId = R.string.low_importance;
importanceDescResId = R.string.notification_importance_low;
break;
case 3:
importanceNameResId = R.string.min_importance;
importanceDescResId = R.string.notification_importance_min;
break;
default:
Log.e(TAG, "Too many importance groups in this layout.");
break;
}
final ViewGroup importanceChildGroup = (ViewGroup) importanceTextGroup.getChildAt(i);
((TextView) importanceChildGroup.getChildAt(0)).setText(importanceNameResId);
((TextView) importanceChildGroup.getChildAt(1)).setText(importanceDescResId);
}
// Top-level importance group
mImportanceGroup = findViewById(R.id.importance);
mChannelDisabled = findViewById(R.id.channel_disabled);
updateImportanceGroup();
}
public boolean hasImportanceChanged() {
return mStartingUserImportance != getSelectedImportance();
}
public void saveImportance() {
int selectedImportance = getSelectedImportance();
if (selectedImportance == mStartingUserImportance) {
return;
}
MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
selectedImportance - mStartingUserImportance);
mNotificationChannel.setImportance(selectedImportance);
try {
mINotificationManager.updateNotificationChannelForPackage(
mStatusBarNotification.getPackageName(), mStatusBarNotification.getUid(),
mNotificationChannel);
} catch (RemoteException e) {
// :(
}
}
private int getSelectedImportance() {
if (!mChannelEnabledSwitch.isChecked()) {
return NotificationManager.IMPORTANCE_NONE;
} else if (mMinImportanceButton.isChecked()) {
return NotificationManager.IMPORTANCE_MIN;
} else if (mLowImportanceButton.isChecked()) {
return NotificationManager.IMPORTANCE_LOW;
} else if (mDefaultImportanceButton.isChecked()) {
return NotificationManager.IMPORTANCE_DEFAULT;
} else if (mHighImportanceButton.isChecked()) {
return NotificationManager.IMPORTANCE_HIGH;
} else {
return NotificationManager.IMPORTANCE_UNSPECIFIED;
}
}
private void bindToggles(final View importanceButtons, final int importance,
final boolean nonBlockable) {
// Enabled Switch
mChannelEnabledSwitch = (Switch) findViewById(R.id.channel_enabled_switch);
mChannelEnabledSwitch.setChecked(importance != NotificationManager.IMPORTANCE_NONE);
mChannelEnabledSwitch.setVisibility(nonBlockable ? View.INVISIBLE : View.VISIBLE);
// Importance Buttons
mMinImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.min_importance);
mLowImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.low_importance);
mDefaultImportanceButton = (RadioButton) importanceButtons
.findViewById(R.id.default_importance);
mHighImportanceButton = (RadioButton) importanceButtons.findViewById(R.id.high_importance);
// Set to current importance setting
switch (importance) {
case NotificationManager.IMPORTANCE_UNSPECIFIED:
case NotificationManager.IMPORTANCE_NONE:
break;
case NotificationManager.IMPORTANCE_MIN:
mMinImportanceButton.setChecked(true);
break;
case NotificationManager.IMPORTANCE_LOW:
mLowImportanceButton.setChecked(true);
break;
case NotificationManager.IMPORTANCE_DEFAULT:
mDefaultImportanceButton.setChecked(true);
break;
case NotificationManager.IMPORTANCE_HIGH:
case NotificationManager.IMPORTANCE_MAX:
mHighImportanceButton.setChecked(true);
break;
}
// Callback when checked.
mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
mGutsInteractionListener.onInteraction(NotificationInfo.this);
updateImportanceGroup();
});
((RadioGroup) importanceButtons).setOnCheckedChangeListener(
(buttonView, isChecked) -> {
mGutsInteractionListener.onInteraction(NotificationInfo.this);
});
}
private void updateImportanceGroup() {
final boolean disabled = getSelectedImportance() == NotificationManager.IMPORTANCE_NONE;
mImportanceGroup.setVisibility(disabled ? View.GONE : View.VISIBLE);
mChannelDisabled.setVisibility(disabled ? View.VISIBLE : View.GONE);
}
public void closeControls() {
if (mGutsInteractionListener != null) {
mGutsInteractionListener.closeGuts(this);
}
}
@Override
public void setInteractionListener(GutsInteractionListener listener) {
mGutsInteractionListener = listener;
}
@Override
public View getContentView() {
return this;
}
@Override
public boolean handleCloseControls() {
return false;
}
}

View File

@@ -0,0 +1,392 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import java.util.ArrayList;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.OnMenuClickListener;
public class NotificationMenuRow extends FrameLayout
implements PluginListener<NotificationMenuRowProvider>, View.OnClickListener {
private static final int ICON_ALPHA_ANIM_DURATION = 200;
private ExpandableNotificationRow mParent;
private OnMenuClickListener mListener;
private NotificationMenuRowProvider mMenuProvider;
private ArrayList<MenuItem> mMenuItems = new ArrayList<>();
private ValueAnimator mFadeAnimator;
private boolean mMenuFadedIn = false;
private boolean mAnimating = false;
private boolean mOnLeft = true;
private boolean mDismissing = false;
private boolean mSnapping = false;
private boolean mIconsPlaced = false;
private int[] mIconLocation = new int[2];
private int[] mParentLocation = new int[2];
private float mHorizSpaceForIcon;
private int mVertSpaceForIcons;
private int mIconPadding;
private int mIconTint;
private float mAlpha = 0f;
public NotificationMenuRow(Context context) {
this(context, null);
}
public NotificationMenuRow(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public NotificationMenuRow(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public NotificationMenuRow(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs);
PluginManager.getInstance(getContext()).addPluginListener(
NotificationMenuRowProvider.ACTION, this,
NotificationMenuRowProvider.VERSION, false /* Allow multiple */);
mMenuItems.addAll(getDefaultNotificationMenuItems());
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
final Resources res = getResources();
mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
mIconTint = res.getColor(R.color.notification_gear_color);
updateMenu(false /* notify */);
}
public static MenuItem getLongpressMenuItem(Context context) {
Resources res = context.getResources();
Drawable settingsIcon = res.getDrawable(R.drawable.ic_settings);
String settingsDescription = res.getString(R.string.notification_menu_gear_description);
NotificationInfo settingsContent = (NotificationInfo) LayoutInflater.from(context).inflate(
R.layout.notification_info, null, false);
MenuItem settings = new MenuItem(settingsIcon, settingsDescription, settingsContent);
return settings;
}
public ArrayList<MenuItem> getDefaultNotificationMenuItems() {
ArrayList<MenuItem> items = new ArrayList<MenuItem>();
Resources res = getResources();
Drawable snoozeIcon = res.getDrawable(R.drawable.ic_snooze);
NotificationSnooze content = (NotificationSnooze) LayoutInflater.from(mContext)
.inflate(R.layout.notification_snooze, null, false);
String snoozeDescription = res.getString(R.string.notification_menu_snooze_description);
MenuItem snooze = new MenuItem(snoozeIcon, snoozeDescription, content);
items.add(snooze);
Drawable settingsIcon = res.getDrawable(R.drawable.ic_settings);
String settingsDescription = res.getString(R.string.notification_menu_gear_description);
NotificationInfo settingsContent = (NotificationInfo) LayoutInflater.from(mContext).inflate(
R.layout.notification_info, null, false);
MenuItem settings = new MenuItem(settingsIcon, settingsDescription, settingsContent);
items.add(settings);
return items;
}
private void updateMenu(boolean notify) {
removeAllViews();
mMenuItems.clear();
if (mMenuProvider != null) {
mMenuItems.addAll(mMenuProvider.getMenuItems(getContext()));
}
mMenuItems.addAll(getDefaultNotificationMenuItems());
for (int i = 0; i < mMenuItems.size(); i++) {
final View v = createMenuView(mMenuItems.get(i));
mMenuItems.get(i).menuView = v;
}
resetState(notify);
}
private View createMenuView(MenuItem item) {
AlphaOptimizedImageView iv = new AlphaOptimizedImageView(getContext());
addView(iv);
iv.setPadding(mIconPadding, mIconPadding, mIconPadding, mIconPadding);
iv.setImageDrawable(item.icon);
iv.setOnClickListener(this);
iv.setColorFilter(mIconTint);
iv.setAlpha(mAlpha);
FrameLayout.LayoutParams lp = (LayoutParams) iv.getLayoutParams();
lp.width = (int) mHorizSpaceForIcon;
lp.height = (int) mHorizSpaceForIcon;
return iv;
}
public void resetState(boolean notify) {
setMenuAlpha(0f);
mIconsPlaced = false;
mMenuFadedIn = false;
mAnimating = false;
mSnapping = false;
mDismissing = false;
setMenuLocation(mOnLeft ? 1 : -1 /* on left */);
if (mListener != null && notify) {
mListener.onMenuReset(mParent);
}
}
public void setMenuClickListener(OnMenuClickListener listener) {
mListener = listener;
}
public void setNotificationRowParent(ExpandableNotificationRow parent) {
mParent = parent;
setMenuLocation(mOnLeft ? 1 : -1);
}
public void setAppName(String appName) {
Resources res = getResources();
final int count = mMenuItems.size();
for (int i = 0; i < count; i++) {
MenuItem item = mMenuItems.get(i);
String description = String.format(
res.getString(R.string.notification_menu_accessibility),
appName, item.menuDescription);
item.menuView.setContentDescription(description);
}
}
public ExpandableNotificationRow getNotificationParent() {
return mParent;
}
public void setMenuAlpha(float alpha) {
mAlpha = alpha;
if (alpha == 0) {
mMenuFadedIn = false; // Can fade in again once it's gone.
setVisibility(View.INVISIBLE);
} else {
setVisibility(View.VISIBLE);
}
final int count = getChildCount();
for (int i = 0; i < count; i++) {
getChildAt(i).setAlpha(mAlpha);
}
}
/**
* Returns whether the menu is displayed on the left side of the view or not.
*/
public boolean isMenuOnLeft() {
return mOnLeft;
}
/**
* Returns the horizontal space in pixels required to display the menu.
*/
public float getSpaceForMenu() {
return mHorizSpaceForIcon * getChildCount();
}
/**
* Indicates whether the menu is visible at 1 alpha. Does not indicate if entire view is
* visible.
*/
public boolean isVisible() {
return mAlpha > 0;
}
public void cancelFadeAnimator() {
if (mFadeAnimator != null) {
mFadeAnimator.cancel();
}
}
public void updateMenuAlpha(final float transX, final float size) {
if (mAnimating || !mMenuFadedIn) {
// Don't adjust when animating, or if the menu hasn't been shown yet.
return;
}
final float fadeThreshold = size * 0.3f;
final float absTrans = Math.abs(transX);
float desiredAlpha = 0;
if (absTrans == 0) {
desiredAlpha = 0;
} else if (absTrans <= fadeThreshold) {
desiredAlpha = 1;
} else {
desiredAlpha = 1 - ((absTrans - fadeThreshold) / (size - fadeThreshold));
}
setMenuAlpha(desiredAlpha);
}
public void fadeInMenu(final boolean fromLeft, final float transX,
final float notiThreshold) {
if (mDismissing || mAnimating) {
return;
}
if (isMenuLocationChange(transX)) {
setMenuAlpha(0f);
}
setMenuLocation((int) transX);
mFadeAnimator = ValueAnimator.ofFloat(mAlpha, 1);
mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
final float absTrans = Math.abs(transX);
boolean pastGear = (fromLeft && transX <= notiThreshold)
|| (!fromLeft && absTrans <= notiThreshold);
if (pastGear && !mMenuFadedIn) {
setMenuAlpha((float) animation.getAnimatedValue());
}
}
});
mFadeAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mAnimating = true;
}
@Override
public void onAnimationCancel(Animator animation) {
// TODO should animate back to 0f from current alpha
setMenuAlpha(0f);
}
@Override
public void onAnimationEnd(Animator animation) {
mAnimating = false;
mMenuFadedIn = mAlpha == 1;
}
});
mFadeAnimator.setInterpolator(Interpolators.ALPHA_IN);
mFadeAnimator.setDuration(ICON_ALPHA_ANIM_DURATION);
mFadeAnimator.start();
}
public void updateVerticalLocation() {
if (mParent == null || mMenuItems.size() == 0) {
return;
}
int parentHeight = mParent.getCollapsedHeight();
float translationY;
if (parentHeight < mVertSpaceForIcons) {
translationY = (parentHeight / 2) - (mHorizSpaceForIcon / 2);
} else {
translationY = (mVertSpaceForIcons - mHorizSpaceForIcon) / 2;
}
setTranslationY(translationY);
}
@Override
public void onRtlPropertiesChanged(int layoutDirection) {
mIconsPlaced = false;
setMenuLocation(mOnLeft ? 1 : -1);
}
public void setMenuLocation(int translation) {
boolean onLeft = translation > 0;
if ((mIconsPlaced && onLeft == mOnLeft) || mSnapping || mParent == null) {
// Do nothing
return;
}
final boolean isRtl = mParent.isLayoutRtl();
final int count = getChildCount();
final int width = getWidth();
for (int i = 0; i < count; i++) {
final View v = getChildAt(i);
final float left = isRtl
? -(width - mHorizSpaceForIcon * (i + 1))
: i * mHorizSpaceForIcon;
final float right = isRtl
? -i * mHorizSpaceForIcon
: width - (mHorizSpaceForIcon * (i + 1));
v.setTranslationX(onLeft ? left : right);
}
mOnLeft = onLeft;
mIconsPlaced = true;
}
public boolean isMenuLocationChange(float translation) {
boolean onLeft = translation > mIconPadding;
boolean onRight = translation < -mIconPadding;
if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) {
return true;
}
return false;
}
public void setDismissing() {
mDismissing = true;
}
public void setSnapping(boolean snapping) {
mSnapping = snapping;
}
@Override
public void onClick(View v) {
if (mListener == null) {
// Nothing to do
return;
}
v.getLocationOnScreen(mIconLocation);
mParent.getLocationOnScreen(mParentLocation);
final int centerX = (int) (mHorizSpaceForIcon / 2);
final int centerY = (int) (v.getTranslationY() * 2 + v.getHeight()) / 2;
final int x = mIconLocation[0] - mParentLocation[0] + centerX;
final int y = mIconLocation[1] - mParentLocation[1] + centerY;
final int index = indexOfChild(v);
mListener.onMenuClicked(mParent, x, y, mMenuItems.get(index));
}
@Override
public void onPluginConnected(NotificationMenuRowProvider plugin, Context pluginContext) {
mMenuProvider = plugin;
updateMenu(false /* notify */);
}
@Override
public void onPluginDisconnected(NotificationMenuRowProvider plugin) {
mMenuProvider = null;
updateMenu(false /* notify */);
}
}

View File

@@ -1,300 +0,0 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
public class NotificationSettingsIconRow extends FrameLayout implements View.OnClickListener {
private static final int GEAR_ALPHA_ANIM_DURATION = 200;
public interface SettingsIconRowListener {
/**
* Called when the gear behind a notification is touched.
*/
public void onGearTouched(ExpandableNotificationRow row, int x, int y);
/**
* Called when a notification is slid back over the gear.
*/
public void onSettingsIconRowReset(ExpandableNotificationRow row);
}
private ExpandableNotificationRow mParent;
private AlphaOptimizedImageView mGearIcon;
private float mHorizSpaceForGear;
private SettingsIconRowListener mListener;
private ValueAnimator mFadeAnimator;
private boolean mSettingsFadedIn = false;
private boolean mAnimating = false;
private boolean mOnLeft = true;
private boolean mDismissing = false;
private boolean mSnapping = false;
private boolean mIconPlaced = false;
private int[] mGearLocation = new int[2];
private int[] mParentLocation = new int[2];
private int mVertSpaceForGear;
public NotificationSettingsIconRow(Context context) {
this(context, null);
}
public NotificationSettingsIconRow(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public NotificationSettingsIconRow(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public NotificationSettingsIconRow(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mGearIcon = (AlphaOptimizedImageView) findViewById(R.id.gear_icon);
mGearIcon.setOnClickListener(this);
setOnClickListener(this);
mHorizSpaceForGear =
getResources().getDimensionPixelOffset(R.dimen.notification_gear_width);
mVertSpaceForGear = getResources().getDimensionPixelOffset(R.dimen.notification_min_height);
resetState();
}
public void resetState() {
setGearAlpha(0f);
mIconPlaced = false;
mSettingsFadedIn = false;
mAnimating = false;
mSnapping = false;
mDismissing = false;
setIconLocation(true /* on left */);
if (mListener != null) {
mListener.onSettingsIconRowReset(mParent);
}
}
public void setGearListener(SettingsIconRowListener listener) {
mListener = listener;
}
public void setNotificationRowParent(ExpandableNotificationRow parent) {
mParent = parent;
setIconLocation(mOnLeft);
}
public void setAppName(String appName) {
Resources res = getResources();
String description = String.format(res.getString(R.string.notification_gear_accessibility),
appName);
mGearIcon.setContentDescription(description);
}
public ExpandableNotificationRow getNotificationParent() {
return mParent;
}
public void setGearAlpha(float alpha) {
if (alpha == 0) {
mSettingsFadedIn = false; // Can fade in again once it's gone.
setVisibility(View.INVISIBLE);
} else {
setVisibility(View.VISIBLE);
}
mGearIcon.setAlpha(alpha);
}
/**
* Returns whether the icon is on the left side of the view or not.
*/
public boolean isIconOnLeft() {
return mOnLeft;
}
/**
* Returns the horizontal space in pixels required to display the gear behind a notification.
*/
public float getSpaceForGear() {
return mHorizSpaceForGear;
}
/**
* Indicates whether the gear is visible at 1 alpha. Does not indicate
* if entire view is visible.
*/
public boolean isVisible() {
return mGearIcon.getAlpha() > 0;
}
public void cancelFadeAnimator() {
if (mFadeAnimator != null) {
mFadeAnimator.cancel();
}
}
public void updateSettingsIcons(final float transX, final float size) {
if (mAnimating || !mSettingsFadedIn) {
// Don't adjust when animating, or if the gear hasn't been shown yet.
return;
}
final float fadeThreshold = size * 0.3f;
final float absTrans = Math.abs(transX);
float desiredAlpha = 0;
if (absTrans == 0) {
desiredAlpha = 0;
} else if (absTrans <= fadeThreshold) {
desiredAlpha = 1;
} else {
desiredAlpha = 1 - ((absTrans - fadeThreshold) / (size - fadeThreshold));
}
setGearAlpha(desiredAlpha);
}
public void fadeInSettings(final boolean fromLeft, final float transX,
final float notiThreshold) {
if (mDismissing || mAnimating) {
return;
}
if (isIconLocationChange(transX)) {
setGearAlpha(0f);
}
setIconLocation(transX > 0 /* fromLeft */);
mFadeAnimator = ValueAnimator.ofFloat(mGearIcon.getAlpha(), 1);
mFadeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
final float absTrans = Math.abs(transX);
boolean pastGear = (fromLeft && transX <= notiThreshold)
|| (!fromLeft && absTrans <= notiThreshold);
if (pastGear && !mSettingsFadedIn) {
setGearAlpha((float) animation.getAnimatedValue());
}
}
});
mFadeAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mAnimating = true;
}
@Override
public void onAnimationCancel(Animator animation) {
// TODO should animate back to 0f from current alpha
mGearIcon.setAlpha(0f);
}
@Override
public void onAnimationEnd(Animator animation) {
mAnimating = false;
mSettingsFadedIn = mGearIcon.getAlpha() == 1;
}
});
mFadeAnimator.setInterpolator(Interpolators.ALPHA_IN);
mFadeAnimator.setDuration(GEAR_ALPHA_ANIM_DURATION);
mFadeAnimator.start();
}
public void updateVerticalLocation() {
if (mParent == null) {
return;
}
int parentHeight = mParent.getCollapsedHeight();
if (parentHeight < mVertSpaceForGear) {
mGearIcon.setTranslationY((parentHeight / 2) - (mGearIcon.getHeight() / 2));
} else {
mGearIcon.setTranslationY((mVertSpaceForGear - mGearIcon.getHeight()) / 2);
}
}
@Override
public void onRtlPropertiesChanged(int layoutDirection) {
setIconLocation(mOnLeft);
}
public void setIconLocation(boolean onLeft) {
if ((mIconPlaced && onLeft == mOnLeft) || mSnapping || mParent == null
|| mGearIcon.getWidth() == 0) {
// Do nothing
return;
}
final boolean isRtl = mParent.isLayoutRtl();
final float left = isRtl
? -(mParent.getWidth() - mHorizSpaceForGear)
: 0;
final float right = isRtl
? 0
: mParent.getWidth() - mHorizSpaceForGear;
final float centerX = ((mHorizSpaceForGear - mGearIcon.getWidth()) / 2);
setTranslationX(onLeft ? left + centerX : right + centerX);
mOnLeft = onLeft;
mIconPlaced = true;
}
public boolean isIconLocationChange(float translation) {
boolean onLeft = translation > mGearIcon.getPaddingStart();
boolean onRight = translation < -mGearIcon.getPaddingStart();
if ((mOnLeft && onRight) || (!mOnLeft && onLeft)) {
return true;
}
return false;
}
public void setDismissing() {
mDismissing = true;
}
public void setSnapping(boolean snapping) {
mSnapping = snapping;
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.gear_icon) {
if (mListener != null) {
mGearIcon.getLocationOnScreen(mGearLocation);
mParent.getLocationOnScreen(mParentLocation);
final int centerX = (int) (mHorizSpaceForGear / 2);
final int centerY =
(int) (mGearIcon.getTranslationY() * 2 + mGearIcon.getHeight())/ 2;
final int x = mGearLocation[0] - mParentLocation[0] + centerX;
final int y = mGearLocation[1] - mParentLocation[1] + centerY;
mListener.onGearTouched(mParent, x, y);
}
} else {
// Do nothing when the background is touched.
}
}
}

View File

@@ -0,0 +1,176 @@
package com.android.systemui.statusbar;
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
import java.util.ArrayList;
import java.util.Calendar;
import java.util.concurrent.TimeUnit;
import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsInteractionListener;
import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeListener;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RadioGroup;
import android.widget.RadioGroup.OnCheckedChangeListener;
import android.widget.TextView;
import android.widget.Toast;
import com.android.systemui.R;
public class NotificationSnooze extends LinearLayout
implements NotificationMenuRowProvider.SnoozeGutsContent, View.OnClickListener {
private GutsInteractionListener mGutsInteractionListener;
private SnoozeListener mSnoozeListener;
private StatusBarNotification mSbn;
private TextView mSelectedOption;
private TextView mUndo;
private ViewGroup mSnoozeOptionView;
private long mTimeToSnooze;
// Default is the first option in this list
private static final int[] SNOOZE_OPTIONS = {
R.string.snooze_option_15_min, R.string.snooze_option_30_min,
R.string.snooze_option_1_hour
};
public NotificationSnooze(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
// Create the different options based on list
createOptionViews();
// Snackbar
mSelectedOption = (TextView) findViewById(R.id.snooze_option_default);
mSelectedOption.setOnClickListener(this);
mUndo = (TextView) findViewById(R.id.undo);
mUndo.setOnClickListener(this);
// Default to first option in list
setTimeToSnooze(SNOOZE_OPTIONS[0]);
}
private void createOptionViews() {
mSnoozeOptionView = (ViewGroup) findViewById(R.id.snooze_options);
mSnoozeOptionView.setVisibility(View.GONE);
final Resources res = getResources();
final int textSize = res.getDimensionPixelSize(R.dimen.snooze_option_text_size);
final int p = res.getDimensionPixelSize(R.dimen.snooze_option_padding);
for (int i = 0; i < SNOOZE_OPTIONS.length; i++) {
TextView tv = new TextView(getContext());
tv.setTextColor(Color.WHITE);
tv.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
tv.setPadding(p, p, p, p);
mSnoozeOptionView.addView(tv);
tv.setText(SNOOZE_OPTIONS[i]);
tv.setTag(SNOOZE_OPTIONS[i]);
tv.setOnClickListener(this);
}
}
private void showSnoozeOptions(boolean show) {
mSelectedOption.setVisibility(show ? View.GONE : View.VISIBLE);
mUndo.setVisibility(show ? View.GONE : View.VISIBLE);
mSnoozeOptionView.setVisibility(show ? View.VISIBLE : View.GONE);
}
private void setTimeToSnooze(int optionId) {
long snoozeUntilMillis = Calendar.getInstance().getTimeInMillis();
switch (optionId) {
case R.string.snooze_option_15_min:
snoozeUntilMillis += TimeUnit.MINUTES.toMillis(15);
break;
case R.string.snooze_option_30_min:
snoozeUntilMillis += TimeUnit.MINUTES.toMillis(30);
break;
case R.string.snooze_option_1_hour:
snoozeUntilMillis += TimeUnit.MINUTES.toMillis(60);
break;
}
mTimeToSnooze = snoozeUntilMillis;
final Resources res = getResources();
String selectedString = String.format(
res.getString(R.string.snoozed_for_time), res.getString(optionId));
mSelectedOption.setText(selectedString);
showSnoozeOptions(false);
}
@Override
public void onClick(View v) {
if (mGutsInteractionListener != null) {
mGutsInteractionListener.onInteraction(this);
}
final int id = v.getId();
final Integer tag = (Integer) v.getTag();
if (tag != null) {
// From the option list
setTimeToSnooze(tag);
} else if (id == R.id.snooze_option_default) {
// Show more snooze options
showSnoozeOptions(true);
} else if (id == R.id.undo) {
mTimeToSnooze = -1;
mGutsInteractionListener.closeGuts(this);
}
}
@Override
public View getContentView() {
return this;
}
@Override
public void setStatusBarNotification(StatusBarNotification sbn) {
mSbn = sbn;
}
@Override
public void setInteractionListener(GutsInteractionListener listener) {
mGutsInteractionListener = listener;
}
@Override
public void setSnoozeListener(SnoozeListener listener) {
mSnoozeListener = listener;
}
@Override
public boolean handleCloseControls() {
// When snooze is closed (i.e. there was interaction outside of the notification)
// then we commit the snooze action.
if (mSnoozeListener != null && mTimeToSnooze != -1) {
mSnoozeListener.snoozeNotification(mSbn, mTimeToSnooze);
return true;
}
return false;
}
}

View File

@@ -141,6 +141,7 @@ import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.ActivityStarter;
import com.android.systemui.plugins.qs.QS.BaseStatusBarHeader;
import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.SnoozeListener;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.recents.ScreenPinningRequest;
@@ -205,7 +206,7 @@ import java.util.Map;
public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
OnHeadsUpChangedListener, VisualStabilityManager.Callback {
OnHeadsUpChangedListener, VisualStabilityManager.Callback, SnoozeListener {
static final String TAG = "PhoneStatusBar";
public static final boolean DEBUG = BaseStatusBar.DEBUG;
public static final boolean SPEW = false;
@@ -4858,4 +4859,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
@Override
public SnoozeListener getSnoozeListener() {
return this;
}
@Override
public void snoozeNotification(StatusBarNotification sbn, long snoozeUntil) {
setNotificationSnoozed(sbn, snoozeUntil);
}
}

View File

@@ -63,14 +63,15 @@ import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.SwipeHelper;
import com.android.systemui.classifier.FalsingManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider;
import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.MenuItem;
import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.DismissView;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.NotificationGuts;
import com.android.systemui.statusbar.NotificationSettingsIconRow;
import com.android.systemui.statusbar.NotificationSettingsIconRow.SettingsIconRowListener;
import com.android.systemui.statusbar.NotificationMenuRow;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StackScrollerDecorView;
import com.android.systemui.statusbar.StatusBarState;
@@ -94,7 +95,8 @@ import java.util.HashSet;
public class NotificationStackScrollLayout extends ViewGroup
implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
SettingsIconRowListener, ScrollContainer, VisibilityLocationProvider {
NotificationMenuRowProvider.OnMenuClickListener, ScrollContainer,
VisibilityLocationProvider {
public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
private static final String TAG = "StackScroller";
@@ -224,7 +226,7 @@ public class NotificationStackScrollLayout extends ViewGroup
private int mMaxScrollAfterExpand;
private SwipeHelper.LongPressListener mLongPressListener;
private NotificationSettingsIconRow mCurrIconRow;
private NotificationMenuRow mCurrIconRow;
private View mTranslatingParentView;
private View mGearExposedView;
@@ -399,16 +401,20 @@ public class NotificationStackScrollLayout extends ViewGroup
}
@Override
public void onGearTouched(ExpandableNotificationRow row, int x, int y) {
if (mLongPressListener != null) {
public void onMenuClicked(View view, int x, int y, MenuItem item) {
if (mLongPressListener == null) {
return;
}
if (view instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) view;
MetricsLogger.action(mContext, MetricsEvent.ACTION_TOUCH_GEAR,
row.getStatusBarNotification().getPackageName());
mLongPressListener.onLongPress(row, x, y);
}
mLongPressListener.onLongPress(view, x, y, item);
}
@Override
public void onSettingsIconRowReset(ExpandableNotificationRow row) {
public void onMenuReset(View row) {
if (mTranslatingParentView != null && row == mTranslatingParentView) {
mSwipeHelper.setSnappedToGear(false);
mGearExposedView = null;
@@ -425,7 +431,7 @@ public class NotificationStackScrollLayout extends ViewGroup
if (DEBUG) {
int y = mTopPadding;
canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
y = (int) getLayoutHeight();
y = getLayoutHeight();
canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
y = getHeight() - getEmptyBottomMargin();
canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
@@ -911,7 +917,7 @@ public class NotificationStackScrollLayout extends ViewGroup
mDragAnimPendingChildren.remove(animView);
}
if (mCurrIconRow != null && targetLeft == 0) {
mCurrIconRow.resetState();
mCurrIconRow.resetState(true /* notify */);
mCurrIconRow = null;
}
}
@@ -4121,7 +4127,7 @@ public class NotificationStackScrollLayout extends ViewGroup
if (currView instanceof ExpandableNotificationRow) {
// Set the listener for the current row's gear
mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow();
mCurrIconRow.setGearListener(NotificationStackScrollLayout.this);
mCurrIconRow.setMenuClickListener(NotificationStackScrollLayout.this);
}
}
@@ -4133,9 +4139,9 @@ public class NotificationStackScrollLayout extends ViewGroup
mCurrIconRow.setSnapping(false); // If we're moving, we're not snapping.
// If the gear is visible and the movement is towards it it's not a location change.
boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isIconOnLeft();
boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isMenuOnLeft();
boolean locationChange = isTowardsGear(translation, onLeft)
? false : mCurrIconRow.isIconLocationChange(translation);
? false : mCurrIconRow.isMenuLocationChange(translation);
if (locationChange) {
// Don't consider it "snapped" if location has changed.
setSnappedToGear(false);
@@ -4146,8 +4152,8 @@ public class NotificationStackScrollLayout extends ViewGroup
mCheckForDrag = null;
} else {
// Check scheduled, reset alpha and update location; check will fade it in
mCurrIconRow.setGearAlpha(0f);
mCurrIconRow.setIconLocation(translation > 0 /* onLeft */);
mCurrIconRow.setMenuAlpha(0f);
mCurrIconRow.setMenuLocation((int) translation);
}
}
}
@@ -4198,14 +4204,14 @@ public class NotificationStackScrollLayout extends ViewGroup
return false; // Let SwipeHelper handle it.
}
boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isIconOnLeft());
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;
if (mGearSnappedTo && mCurrIconRow.isVisible()) {
if (mGearSnappedOnLeft == mCurrIconRow.isIconOnLeft()) {
if (mGearSnappedOnLeft == mCurrIconRow.isMenuOnLeft()) {
boolean coveringGear =
Math.abs(getTranslation(animView)) <= getSpaceForGear(animView) * 0.6f;
if (gestureTowardsGear || coveringGear) {
@@ -4249,7 +4255,7 @@ public class NotificationStackScrollLayout extends ViewGroup
private void snapToGear(View animView, float velocity) {
final float snapBackThreshold = getSpaceForGear(animView);
final float target = mCurrIconRow.isIconOnLeft() ? snapBackThreshold
final float target = mCurrIconRow.isMenuOnLeft() ? snapBackThreshold
: -snapBackThreshold;
mGearExposedView = mTranslatingParentView;
if (animView instanceof ExpandableNotificationRow) {
@@ -4280,7 +4286,7 @@ public class NotificationStackScrollLayout extends ViewGroup
final float multiplier = canChildBeDismissed(animView) ? 0.4f : 0.2f;
final float snapBackThreshold = getSpaceForGear(animView) * multiplier;
final float translation = getTranslation(animView);
return !swipedFarEnough() && mCurrIconRow.isVisible() && (mCurrIconRow.isIconOnLeft()
return !swipedFarEnough() && mCurrIconRow.isVisible() && (mCurrIconRow.isMenuOnLeft()
? translation > snapBackThreshold
: translation < -snapBackThreshold);
}
@@ -4349,7 +4355,7 @@ public class NotificationStackScrollLayout extends ViewGroup
* Indicates the the gear has been snapped to.
*/
private void setSnappedToGear(boolean snapped) {
mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isIconOnLeft() : false;
mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isMenuOnLeft() : false;
mGearSnappedTo = snapped && mCurrIconRow != null;
}
@@ -4389,11 +4395,11 @@ public class NotificationStackScrollLayout extends ViewGroup
final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView);
final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
if ((mCurrIconRow != null && (!mCurrIconRow.isVisible()
|| mCurrIconRow.isIconLocationChange(translation)))
|| mCurrIconRow.isMenuLocationChange(translation)))
&& absTransX >= bounceBackToGearWidth * 0.4
&& absTransX < notiThreshold) {
// Fade in the gear
mCurrIconRow.fadeInSettings(translation > 0 /* fromLeft */, translation,
mCurrIconRow.fadeInMenu(translation > 0 /* fromLeft */, translation,
notiThreshold);
}
}

View File

@@ -63,7 +63,7 @@ public class NotificationGutsTest {
private static final String TEST_CHANNEL = "test_channel";
private static final String TEST_CHANNEL_NAME = "TEST CHANNEL NAME";
private NotificationGuts mNotificationGuts;
private NotificationInfo mNotificationInfo;
private final INotificationManager mMockINotificationManager = mock(INotificationManager.class);
private final PackageManager mMockPackageManager = mock(PackageManager.class);
private NotificationChannel mNotificationChannel;
@@ -76,7 +76,7 @@ public class NotificationGutsTest {
// Inflate the layout
final LayoutInflater layoutInflater =
LayoutInflater.from(InstrumentationRegistry.getTargetContext());
mNotificationGuts = (NotificationGuts) layoutInflater.inflate(R.layout.notification_guts,
mNotificationInfo = (NotificationInfo) layoutInflater.inflate(R.layout.notification_info,
null);
// PackageManager must return a packageInfo and applicationInfo.
@@ -98,18 +98,18 @@ public class NotificationGutsTest {
@UiThreadTest
public void testBindNotification_SetsTextApplicationName() throws Exception {
when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
final TextView textView = (TextView) mNotificationGuts.findViewById(R.id.pkgname);
final TextView textView = (TextView) mNotificationInfo.findViewById(R.id.pkgname);
assertTrue(textView.getText().toString().contains("App Name"));
}
@Test
@UiThreadTest
public void testBindNotification_SetsTextChannelName() throws Exception {
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
final TextView textView = (TextView) mNotificationGuts.findViewById(R.id.channel_name);
final TextView textView = (TextView) mNotificationInfo.findViewById(R.id.channel_name);
assertEquals(TEST_CHANNEL_NAME, textView.getText());
}
@@ -117,12 +117,12 @@ public class NotificationGutsTest {
@UiThreadTest
public void testBindNotification_SetsOnClickListenerForSettings() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel,
(View v, int appUid) -> { latch.countDown(); }, null, null);
final TextView settingsButton =
(TextView) mNotificationGuts.findViewById(R.id.more_settings);
final TextView settingsButton =
(TextView) mNotificationInfo.findViewById(R.id.more_settings);
settingsButton.performClick();
// Verify that listener was triggered.
assertEquals(0, latch.getCount());
@@ -132,12 +132,12 @@ public class NotificationGutsTest {
@UiThreadTest
public void testBindNotification_SetsOnClickListenerForDone() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null,
(View v) -> { latch.countDown(); },
null);
final TextView doneButton = (TextView) mNotificationGuts.findViewById(R.id.done);
final TextView doneButton = (TextView) mNotificationInfo.findViewById(R.id.done);
doneButton.performClick();
// Verify that listener was triggered.
assertEquals(0, latch.getCount());
@@ -146,38 +146,38 @@ public class NotificationGutsTest {
@Test
@UiThreadTest
public void testHasImportanceChanged_DefaultsToFalse() throws Exception {
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
assertFalse(mNotificationGuts.hasImportanceChanged());
assertFalse(mNotificationInfo.hasImportanceChanged());
}
@Test
@UiThreadTest
public void testHasImportanceChanged_ReturnsTrueAfterButtonChecked() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
// Find the high button and check it.
RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
RadioButton highButton = (RadioButton) mNotificationInfo.findViewById(R.id.high_importance);
highButton.setChecked(true);
assertTrue(mNotificationGuts.hasImportanceChanged());
assertTrue(mNotificationInfo.hasImportanceChanged());
}
@Test
@UiThreadTest
public void testImportanceButtonCheckedBasedOnInitialImportance() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_HIGH);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
RadioButton highButton = (RadioButton) mNotificationInfo.findViewById(R.id.high_importance);
assertTrue(highButton.isChecked());
}
@Test
@UiThreadTest
public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception {
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
@@ -187,10 +187,10 @@ public class NotificationGutsTest {
@UiThreadTest
public void testDoesNotUpdateNotificationChannelAfterImportanceChanged() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
RadioButton highButton = (RadioButton) mNotificationInfo.findViewById(R.id.high_importance);
highButton.setChecked(true);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
@@ -199,10 +199,10 @@ public class NotificationGutsTest {
@Test
@UiThreadTest
public void testCloseControls_DoesNotUpdateNotificationChannelIfUnchanged() throws Exception {
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
mNotificationGuts.closeControls(-1, -1, true);
mNotificationInfo.closeControls();
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@@ -211,10 +211,10 @@ public class NotificationGutsTest {
@UiThreadTest
public void testCloseControls_DoesNotUpdateNotificationChannelIfUnspecified() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_UNSPECIFIED);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
mNotificationGuts.closeControls(-1, -1, true);
mNotificationInfo.closeControls();
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@@ -223,12 +223,12 @@ public class NotificationGutsTest {
@UiThreadTest
public void testCloseControls_CallsUpdateNotificationChannelIfChanged() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
RadioButton highButton = (RadioButton) mNotificationInfo.findViewById(R.id.high_importance);
highButton.setChecked(true);
mNotificationGuts.closeControls(-1, -1, true);
mNotificationInfo.closeControls();
verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
eq(TEST_PACKAGE_NAME), anyInt(), eq(mNotificationChannel));
assertEquals(NotificationManager.IMPORTANCE_HIGH, mNotificationChannel.getImportance());
@@ -238,12 +238,12 @@ public class NotificationGutsTest {
@UiThreadTest
public void testCloseControls_DoesNotUpdateNotificationChannelIfSaveFalse() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
RadioButton highButton = (RadioButton) mNotificationInfo.findViewById(R.id.high_importance);
highButton.setChecked(true);
mNotificationGuts.closeControls(-1, -1, false);
mNotificationInfo.closeControls();
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@@ -252,10 +252,10 @@ public class NotificationGutsTest {
@UiThreadTest
public void testEnabledSwitchOnByDefault() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
assertTrue(enabledSwitch.isChecked());
}
@@ -263,10 +263,10 @@ public class NotificationGutsTest {
@UiThreadTest
public void testEnabledSwitchVisibleByDefault() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
assertEquals(View.VISIBLE, enabledSwitch.getVisibility());
}
@@ -274,11 +274,11 @@ public class NotificationGutsTest {
@UiThreadTest
public void testEnabledSwitchInvisibleIfNonBlockable() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null,
Collections.singleton(TEST_PACKAGE_NAME));
Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
assertEquals(View.INVISIBLE, enabledSwitch.getVisibility());
}
@@ -286,13 +286,13 @@ public class NotificationGutsTest {
@UiThreadTest
public void testEnabledSwitchChangedCallsUpdateNotificationChannel() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null,
Collections.singleton(TEST_PACKAGE_NAME));
Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
enabledSwitch.setChecked(false);
mNotificationGuts.closeControls(-1, -1, true);
mNotificationInfo.closeControls();
verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
eq(TEST_PACKAGE_NAME), anyInt(), eq(mNotificationChannel));
}
@@ -301,14 +301,14 @@ public class NotificationGutsTest {
@UiThreadTest
public void testEnabledSwitchOverridesOtherButtons() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, mNotificationChannel, null, null, null);
Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
RadioButton lowButton = (RadioButton) mNotificationGuts.findViewById(R.id.low_importance);
Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);
RadioButton lowButton = (RadioButton) mNotificationInfo.findViewById(R.id.low_importance);
lowButton.setChecked(true);
enabledSwitch.setChecked(false);
mNotificationGuts.closeControls(-1, -1, true);
mNotificationInfo.closeControls();
assertEquals(NotificationManager.IMPORTANCE_NONE, mNotificationChannel.getImportance());
}
}