Merge "Change Notification Inline Settings for Channels"

This commit is contained in:
Geoffrey Pitsch
2016-12-29 15:36:53 +00:00
committed by Android (Google) Code Review
12 changed files with 682 additions and 427 deletions

View File

@@ -16,150 +16,109 @@
-->
<com.android.systemui.statusbar.NotificationGuts
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: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">
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: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 -->
<LinearLayout
android:layout_width="match_parent"
<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:paddingTop="15dp"
android:paddingEnd="8dp"
android:id="@+id/notification_guts_header"
android:orientation="horizontal"
android:layout_gravity="start">
<ImageView
android:id="@+id/app_icon"
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginEnd="6dp"
android:src="@android:drawable/arrow_down_float" />
android:layout_alignParentStart="true"
style="@style/TextAppearance.NotificationGuts.Secondary" />
<TextView
android:id="@+id/pkgname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TextAppearance.NotificationGuts.Header" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/debug_info"
android:layout_weight="0"
style="@style/TextAppearance.NotificationGuts.Header"
android:layout_gravity="bottom|start"
android:visibility="gone" />
</LinearLayout>
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 -->
<RadioGroup
<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:paddingStart="3dp"
android:paddingTop="4dp"
android:paddingEnd="8dp" >
<RadioButton
android:id="@+id/silent_importance"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:paddingStart="22dp"
android:text="@string/show_silently"
style="@style/TextAppearance.NotificationGuts.Radio"
android:buttonTint="@color/notification_guts_buttons" />
<RadioButton
android:id="@+id/block_importance"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:paddingStart="22dp"
android:text="@string/block"
style="@style/TextAppearance.NotificationGuts.Radio"
android:buttonTint="@color/notification_guts_buttons" />
<RadioButton
android:id="@+id/reset_importance"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:paddingStart="22dp"
style="@style/TextAppearance.NotificationGuts.Radio"
android:buttonTint="@color/notification_guts_buttons" />
</RadioGroup>
<!-- Importance slider -->
<LinearLayout
android:id="@+id/importance_slider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical"
android:clickable="false"
android:focusable="false"
android:paddingEnd="8dp"
android:paddingTop="4dp"
android:visibility="gone">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
style="@style/TextAppearance.NotificationGuts.Primary"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:paddingBottom="2dp"/>
<TextView
android:id="@+id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignStart="@android:id/title"
android:textAlignment="viewStart"
style="@style/TextAppearance.NotificationGuts.Secondary"
android:maxLines="3"
android:minLines="2"
android:paddingBottom="4dp" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp" >
<ImageView
android:id="@+id/auto_importance"
android:src="@drawable/notification_auto_importance"
android:layout_gravity="center_vertical|start"
android:layout_width="48dp"
android:layout_height="48dp" />
<SeekBar
android:id="@+id/seekbar"
android:layout_marginStart="56dp"
android:layout_marginEnd="32dp"
android:layout_gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="48dp"
android:focusable="true"
android:background="#00ffffff"
android:progressBackgroundTint="@color/notification_guts_secondary_slider_color"
android:thumbTint="?android:attr/colorAccent"
android:progressTint="?android:attr/colorAccent"
style="@android:style/Widget.Material.SeekBar.Discrete"
android:tickMarkTint="@android:color/black" />
</FrameLayout>
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>
<!-- buttons -->
<!-- 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" >
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"

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2016, The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="@dimen/notification_inline_importance_height"
android:paddingTop="4dp"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:textAlignment="viewStart"
android:singleLine="true"
android:text="@string/high_importance"
android:textAppearance="@style/TextAppearance.NotificationGuts.Primary" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:textAlignment="viewStart"
android:singleLine="true"
android:text="@string/notification_importance_high"
android:textAppearance="@style/TextAppearance.NotificationGuts.Secondary" />
</LinearLayout>

View File

@@ -302,6 +302,9 @@
<!-- The height of the divider between the individual notifications when the notification wants it to be increased. This is currently the case for notification groups -->
<dimen name="notification_divider_height_increased">6dp</dimen>
<!-- The height of an importance selection in the inline notification controls -->
<dimen name="notification_inline_importance_height">55dp</dimen>
<!-- The minimum amount of top overscroll to go to the quick settings. -->
<dimen name="min_top_overscroll_to_qs">36dp</dimen>

View File

@@ -1334,41 +1334,38 @@
\n- Block all notifications from the app
</string>
<!-- Notification importance title, user unspecified status-->
<string name="user_unspecified_importance">Importance: Automatic</string>
<!-- Notification importance title, blocked status-->
<string name="blocked_importance">Importance: Level 0</string>
<!-- Notification Inline Controls: Header for apps that are not yet using notification channels. -->
<string name="notification_header_default_channel">Notifications</string>
<!-- Notification Inline Controls: Shown when a channel's notifications are currently blocked -->
<string name="notification_channel_disabled">You won\'t get these notifications anymore.</string>
<!-- Notification Inline Controls: Header text for describing from which app this notification
originates. The line below this in the layout will display the channel name.
Note for localization: For languages in which the two separate lines cannot be a continuous
sentence, translate this as a separate statement: "[Calendar] notifications" -->
<string name="notification_importance_header_app"><xliff:g id="app" example="Calendar">%s</xliff:g> notifications for</string>
<!-- Notification importance title, min status-->
<string name="min_importance">Importance: Level 1</string>
<string name="min_importance">Low</string>
<!-- Notification importance title, low status-->
<string name="low_importance">Importance: Level 2</string>
<string name="low_importance">Medium</string>
<!-- Notification importance title, normal status-->
<string name="default_importance">Importance: Level 3</string>
<string name="default_importance">High</string>
<!-- Notification importance title, high status-->
<string name="high_importance">Importance: Level 4</string>
<!-- Notification importance title, max status-->
<string name="max_importance">Importance: Level 5</string>
<!-- [CHAR LIMIT=100] Notification Importance slider: blocked importance level description -->
<string name="notification_importance_user_unspecified">App determines importance for each notification.</string>
<!-- [CHAR LIMIT=100] Notification Importance slider: blocked importance level description -->
<string name="notification_importance_blocked">Never show notifications from this app.</string>
<string name="high_importance">Urgent</string>
<!-- [CHAR LIMIT=100] Notification Importance slider: min importance level description -->
<string name="notification_importance_min">No full screen interruption, peeking, sound, or vibration. Hide from lock screen and status bar.</string>
<string name="notification_importance_min">No sound or visual interruption</string>
<!-- [CHAR LIMIT=100] Notification Importance slider: low importance level description -->
<string name="notification_importance_low">No full screen interruption, peeking, sound, or vibration.</string>
<string name="notification_importance_low">Show silently</string>
<!-- [CHAR LIMIT=100] Notification Importance slider: normal importance level description -->
<string name="notification_importance_default">No full screen interruption or peeking.</string>
<string name="notification_importance_default">Make sound</string>
<!-- [CHAR LIMIT=100] Notification Importance slider: high importance level description -->
<string name="notification_importance_high">Always peek. No full screen interruption.</string>
<!-- [CHAR LIMIT=100] Notification Importance slider: max importance level description -->
<string name="notification_importance_max">Always peek, and allow full screen interruption.</string>
<string name="notification_importance_high">Make sound and pop on screen</string>
<!-- Notification: Control panel: Label for button that launches notification settings. [CHAR LIMIT=NONE] -->
<string name="notification_more_settings">More settings</string>

View File

@@ -4,9 +4,9 @@
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.
@@ -359,12 +359,14 @@
</style>
<style name="TextAppearance.NotificationGuts.Header">
<item name="android:alpha">.38</item>
<item name="android:textSize">12sp</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">20sp</item>
</style>
<style name="TextAppearance.NotificationGuts.Secondary">
<item name="android:textColor">?android:attr/textColorSecondary</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:alpha">.38</item>
<item name="android:textSize">12sp</item>
</style>
<style name="TextAppearance.NotificationGuts.Primary">

View File

@@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter;
import android.app.ActivityManager;
import android.app.ActivityManager.StackId;
import android.app.ActivityOptions;
import android.app.INotificationManager;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
@@ -1058,74 +1059,43 @@ public abstract class BaseStatusBar extends SystemUI implements
row.setTag(sbn.getPackageName());
final NotificationGuts guts = row.getGuts();
guts.setClosedListener(this);
final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
final String pkg = sbn.getPackageName();
String appname = pkg;
Drawable pkgicon = null;
int appUid = -1;
try {
final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.MATCH_DISABLED_COMPONENTS);
if (info != null) {
appname = String.valueOf(pmUser.getApplicationLabel(info));
pkgicon = pmUser.getApplicationIcon(info);
appUid = info.uid;
}
} catch (NameNotFoundException e) {
// app is gone, just show package name and generic icon
pkgicon = pmUser.getDefaultActivityIcon();
}
((ImageView) guts.findViewById(R.id.app_icon)).setImageDrawable(pkgicon);
((TextView) guts.findViewById(R.id.pkgname)).setText(appname);
final TextView settingsButton = (TextView) guts.findViewById(R.id.more_settings);
if (appUid >= 0) {
final int appUidF = appUid;
settingsButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
final NotificationGuts.OnSettingsClickListener onSettingsClick =
(View v, int appUid) -> {
MetricsLogger.action(mContext, MetricsEvent.ACTION_NOTE_INFO);
guts.resetFalsingCheck();
startAppNotificationSettingsActivity(pkg, appUidF);
}
});
settingsButton.setText(R.string.notification_more_settings);
} else {
settingsButton.setVisibility(View.GONE);
}
guts.bindImportance(pmUser, sbn, mNonBlockablePkgs,
mNotificationData.getImportance(sbn.getKey()));
final TextView doneButton = (TextView) guts.findViewById(R.id.done);
doneButton.setText(R.string.notification_done);
doneButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(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() {
saveImportanceCloseControls(sbn, row, guts, v);
return true;
}
};
onLockedNotificationImportanceChange(dismissAction);
} else {
saveImportanceCloseControls(sbn, row, guts, v);
}
}
});
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, onSettingsClick, onDoneClick,
mNonBlockablePkgs);
}
private void saveImportanceCloseControls(StatusBarNotification sbn,
private void closeControls(
ExpandableNotificationRow row, NotificationGuts guts, View done) {
guts.resetFalsingCheck();
guts.saveImportance(sbn);
int[] rowLocation = new int[2];
int[] doneLocation = new int[2];
@@ -1222,7 +1192,7 @@ public abstract class BaseStatusBar extends SystemUI implements
public void dismissPopups(int x, int y, boolean resetGear, boolean animate) {
if (mNotificationGutsExposed != null) {
mNotificationGutsExposed.closeControls(x, y, true /* notify */);
mNotificationGutsExposed.closeControls(x, y, true /* save */);
}
if (resetGear) {
mStackScroller.resetExposedGearView(animate, true /* force */);
@@ -1722,13 +1692,6 @@ public abstract class BaseStatusBar extends SystemUI implements
}
entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
if (MULTIUSER_DEBUG) {
TextView debug = (TextView) row.findViewById(R.id.debug_info);
if (debug != null) {
debug.setVisibility(View.VISIBLE);
debug.setText("CU " + mCurrentUserId +" NU " + entry.notification.getUserId());
}
}
entry.row = row;
entry.row.setOnActivatedListener(this);
entry.row.setExpandable(bigContentViewLocal != null);

View File

@@ -1338,7 +1338,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
if (isUserLocked()) {
return getActualHeight();
}
if (mGuts != null && mGuts.areGutsExposed()) {
if (mGuts != null && mGuts.isExposed()) {
return mGuts.getHeight();
} else if ((isChildInGroup() && !isGroupExpanded())) {
return mPrivateLayout.getMinHeight();
@@ -1589,7 +1589,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
}
public boolean areGutsExposed() {
return (mGuts != null && mGuts.areGutsExposed());
return (mGuts != null && mGuts.isExposed());
}
@Override
@@ -1634,7 +1634,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
@Override
public void setActualHeight(int height, boolean notifyListeners) {
super.setActualHeight(height, notifyListeners);
if (mGuts != null && mGuts.areGutsExposed()) {
if (mGuts != null && mGuts.isExposed()) {
mGuts.setActualHeight(height);
return;
}

View File

@@ -19,8 +19,10 @@ package com.android.systemui.statusbar;
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;
@@ -33,13 +35,17 @@ 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.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;
@@ -48,16 +54,14 @@ import com.android.settingslib.Utils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.stack.StackStateAnimator;
import com.android.systemui.tuner.TunerService;
import java.util.Set;
/**
* The guts of a notification revealed when performing a long press.
*/
public class NotificationGuts extends LinearLayout implements TunerService.Tunable {
public static final String SHOW_SLIDER = "show_importance_slider";
public class NotificationGuts extends LinearLayout {
private static final String TAG = "NotificationGuts";
private static final long CLOSE_GUTS_DELAY = 8000;
private Drawable mBackground;
@@ -67,22 +71,20 @@ public class NotificationGuts extends LinearLayout implements TunerService.Tunab
private boolean mExposed;
private INotificationManager mINotificationManager;
private int mStartingUserImportance;
private int mNotificationImportance;
private boolean mShowSlider;
private StatusBarNotification mStatusBarNotification;
private SeekBar mSeekBar;
private ImageView mAutoButton;
private ColorStateList mActiveSliderTint;
private ColorStateList mInactiveSliderTint;
private float mActiveSliderAlpha = 1.0f;
private float mInactiveSliderAlpha;
private TextView mImportanceSummary;
private TextView mImportanceTitle;
private boolean mAuto;
private RadioButton mBlock;
private RadioButton mSilent;
private RadioButton mReset;
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;
@@ -101,29 +103,15 @@ public class NotificationGuts extends LinearLayout implements TunerService.Tunab
@Override
public void run() {
if (mNeedsFalsingProtection && mExposed) {
closeControls(-1 /* x */, -1 /* y */, true /* notify */);
closeControls(-1 /* x */, -1 /* y */, false /* save */);
}
}
};
final TypedArray ta =
context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Theme, 0, 0);
mInactiveSliderAlpha =
ta.getFloat(com.android.internal.R.styleable.Theme_disabledAlpha, 0.5f);
ta.recycle();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
TunerService.get(mContext).addTunable(this, SHOW_SLIDER);
}
@Override
protected void onDetachedFromWindow() {
TunerService.get(mContext).removeTunable(this);
super.onDetachedFromWindow();
}
public void resetFalsingCheck() {
mHandler.removeCallbacks(mFalsingCheck);
if (mNeedsFalsingProtection && mExposed) {
@@ -177,203 +165,213 @@ public class NotificationGuts extends LinearLayout implements TunerService.Tunab
}
}
void bindImportance(final PackageManager pm, final StatusBarNotification sbn,
final Set<String> nonBlockablePkgs, final int importance) {
mINotificationManager = INotificationManager.Stub.asInterface(
ServiceManager.getService(Context.NOTIFICATION_SERVICE));
mStartingUserImportance = NotificationManager.IMPORTANCE_UNSPECIFIED;
mNotificationImportance = importance;
interface OnSettingsClickListener {
void onClick(View v, int appUid);
}
void bindNotification(final PackageManager pm, final INotificationManager iNotificationManager,
final StatusBarNotification sbn, OnSettingsClickListener onSettingsClick,
OnClickListener onDoneClick, final Set<String> nonBlockablePkgs) {
mINotificationManager = iNotificationManager;
mStatusBarNotification = sbn;
final NotificationChannel channel = sbn.getNotificationChannel();
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(sbn.getPackageName(), PackageManager.GET_SIGNATURES);
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(sbn.getPackageName());
nonBlockable |= nonBlockablePkgs.contains(pkg);
}
final View importanceSlider = findViewById(R.id.importance_slider);
final View importanceButtons = findViewById(R.id.importance_buttons);
if (mShowSlider) {
bindSlider(importanceSlider, nonBlockable);
importanceSlider.setVisibility(View.VISIBLE);
importanceButtons.setVisibility(View.GONE);
} else {
bindToggles(importanceButtons, mStartingUserImportance, nonBlockable);
importanceButtons.setVisibility(View.VISIBLE);
importanceSlider.setVisibility(View.GONE);
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();
}
void saveImportance(final StatusBarNotification sbn) {
int progress = getSelectedImportance();
private void saveImportance() {
int selectedImportance = getSelectedImportance();
if (selectedImportance == mStartingUserImportance) {
return;
}
final NotificationChannel channel = mStatusBarNotification.getNotificationChannel();
MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
progress - mStartingUserImportance);
selectedImportance - mStartingUserImportance);
channel.setImportance(selectedImportance);
try {
mINotificationManager.updateNotificationChannelForPackage(
mStatusBarNotification.getPackageName(), mStatusBarNotification.getUid(),
channel);
} catch (RemoteException e) {
// :(
}
}
private int getSelectedImportance() {
if (mSeekBar!= null && mSeekBar.isShown()) {
if (mSeekBar.isEnabled()) {
return mSeekBar.getProgress();
} else {
return NotificationManager.IMPORTANCE_UNSPECIFIED;
}
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 {
if (mBlock.isChecked()) {
return NotificationManager.IMPORTANCE_NONE;
} else if (mSilent.isChecked()) {
return NotificationManager.IMPORTANCE_LOW;
} else {
return NotificationManager.IMPORTANCE_UNSPECIFIED;
}
return NotificationManager.IMPORTANCE_NONE;
}
}
private void bindToggles(final View importanceButtons, final int importance,
final boolean nonBlockable) {
((RadioGroup) importanceButtons).setOnCheckedChangeListener(
new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
resetFalsingCheck();
}
});
mBlock = (RadioButton) importanceButtons.findViewById(R.id.block_importance);
mSilent = (RadioButton) importanceButtons.findViewById(R.id.silent_importance);
mReset = (RadioButton) importanceButtons.findViewById(R.id.reset_importance);
if (nonBlockable) {
mBlock.setVisibility(View.GONE);
mReset.setText(mContext.getString(R.string.do_not_silence));
} else {
mReset.setText(mContext.getString(R.string.do_not_silence_block));
}
mBlock.setText(mContext.getString(R.string.block));
mSilent.setText(mContext.getString(R.string.show_silently));
if (importance == NotificationManager.IMPORTANCE_LOW) {
mSilent.setChecked(true);
} else {
mReset.setChecked(true);
}
}
// Enabled Switch
mChannelEnabledSwitch = (Switch) findViewById(R.id.channel_enabled_switch);
mChannelEnabledSwitch.setChecked(importance != NotificationManager.IMPORTANCE_NONE);
mChannelEnabledSwitch.setVisibility(nonBlockable ? View.INVISIBLE : View.VISIBLE);
private void bindSlider(final View importanceSlider, final boolean nonBlockable) {
mActiveSliderTint = ColorStateList.valueOf(Utils.getColorAccent(mContext));
mInactiveSliderTint = loadColorStateList(R.color.notification_guts_disabled_slider_color);
// 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);
mImportanceSummary = ((TextView) importanceSlider.findViewById(R.id.summary));
mImportanceTitle = ((TextView) importanceSlider.findViewById(R.id.title));
mSeekBar = (SeekBar) importanceSlider.findViewById(R.id.seekbar);
final int minProgress = nonBlockable ?
NotificationManager.IMPORTANCE_MIN
: NotificationManager.IMPORTANCE_NONE;
mSeekBar.setMax(NotificationManager.IMPORTANCE_HIGH);
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
resetFalsingCheck();
if (progress < minProgress) {
seekBar.setProgress(minProgress);
progress = minProgress;
}
updateTitleAndSummary(progress);
if (fromUser) {
MetricsLogger.action(mContext, MetricsEvent.ACTION_MODIFY_IMPORTANCE_SLIDER);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
resetFalsingCheck();
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
// no-op
}
});
mSeekBar.setProgress(mNotificationImportance);
mAutoButton = (ImageView) importanceSlider.findViewById(R.id.auto_importance);
mAutoButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mAuto = !mAuto;
applyAuto();
}
});
mAuto = mStartingUserImportance == NotificationManager.IMPORTANCE_UNSPECIFIED;
applyAuto();
}
private void applyAuto() {
mSeekBar.setEnabled(!mAuto);
final ColorStateList starTint = mAuto ? mActiveSliderTint : mInactiveSliderTint;
final float alpha = mAuto ? mInactiveSliderAlpha : mActiveSliderAlpha;
Drawable icon = mAutoButton.getDrawable().mutate();
icon.setTintList(starTint);
mAutoButton.setImageDrawable(icon);
mSeekBar.setAlpha(alpha);
if (mAuto) {
mSeekBar.setProgress(mNotificationImportance);
mImportanceSummary.setText(mContext.getString(
R.string.notification_importance_user_unspecified));
mImportanceTitle.setText(mContext.getString(
R.string.user_unspecified_importance));
} else {
updateTitleAndSummary(mSeekBar.getProgress());
}
}
private void updateTitleAndSummary(int progress) {
switch (progress) {
// Set to current importance setting
switch (importance) {
case NotificationManager.IMPORTANCE_NONE:
mImportanceSummary.setText(mContext.getString(
R.string.notification_importance_blocked));
mImportanceTitle.setText(mContext.getString(R.string.blocked_importance));
break;
case NotificationManager.IMPORTANCE_MIN:
mImportanceSummary.setText(mContext.getString(
R.string.notification_importance_min));
mImportanceTitle.setText(mContext.getString(R.string.min_importance));
case NotificationManager.IMPORTANCE_UNSPECIFIED:
mMinImportanceButton.setChecked(true);
break;
case NotificationManager.IMPORTANCE_LOW:
mImportanceSummary.setText(mContext.getString(
R.string.notification_importance_low));
mImportanceTitle.setText(mContext.getString(R.string.low_importance));
mLowImportanceButton.setChecked(true);
break;
case NotificationManager.IMPORTANCE_DEFAULT:
mImportanceSummary.setText(mContext.getString(
R.string.notification_importance_default));
mImportanceTitle.setText(mContext.getString(R.string.default_importance));
mDefaultImportanceButton.setChecked(true);
break;
case NotificationManager.IMPORTANCE_HIGH:
case NotificationManager.IMPORTANCE_MAX:
mImportanceSummary.setText(mContext.getString(
R.string.notification_importance_high));
mImportanceTitle.setText(mContext.getString(R.string.high_importance));
mHighImportanceButton.setChecked(true);
break;
}
// Callback when checked.
mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
resetFalsingCheck();
updateImportanceGroup();
});
((RadioGroup) importanceButtons).setOnCheckedChangeListener(
(buttonView, isChecked) -> { resetFalsingCheck(); });
}
private ColorStateList loadColorStateList(int colorResId) {
return ColorStateList.valueOf(mContext.getColor(colorResId));
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 notify) {
public void closeControls(int x, int y, boolean saveImportance) {
if (saveImportance) {
saveImportance();
}
if (getWindowToken() == null) {
if (notify && mListener != null) {
if (mListener != null) {
mListener.onGutsClosed(this);
}
return;
@@ -398,7 +396,7 @@ public class NotificationGuts extends LinearLayout implements TunerService.Tunab
});
a.start();
setExposed(false, mNeedsFalsingProtection);
if (notify && mListener != null) {
if (mListener != null) {
mListener.onGutsClosed(this);
}
}
@@ -442,14 +440,7 @@ public class NotificationGuts extends LinearLayout implements TunerService.Tunab
}
}
public boolean areGutsExposed() {
public boolean isExposed() {
return mExposed;
}
@Override
public void onTuningChanged(String key, String newValue) {
if (SHOW_SLIDER.equals(key)) {
mShowSlider = newValue != null && Integer.parseInt(newValue) != 0;
}
}
}

View File

@@ -138,15 +138,14 @@ public class NotificationChildrenContainer extends ViewGroup {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int ownMaxHeight = mMaxNotificationHeight;
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
int size = MeasureSpec.getSize(heightMeasureSpec);
int newHeightSpec = heightMeasureSpec;
if (hasFixedHeight || isHeightLimited) {
ownMaxHeight = Math.min(ownMaxHeight, size);
newHeightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
}
int newHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST);
int width = MeasureSpec.getSize(widthMeasureSpec);
if (mOverflowNumber != null) {
mOverflowNumber.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),

View File

@@ -27,6 +27,7 @@
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE" />
<uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<application>
<uses-library android:name="android.test.runner" />

View File

@@ -0,0 +1,301 @@
/*
* 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 static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.INotificationManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.service.notification.StatusBarNotification;
import android.support.test.InstrumentationRegistry;
import android.support.test.annotation.UiThreadTest;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.RadioButton;
import android.widget.Switch;
import android.widget.TextView;
import com.android.systemui.R;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.junit.Test;
import org.mockito.Mockito;
import java.util.Collections;
import java.util.concurrent.CountDownLatch;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class NotificationGutsTest {
private static final String TEST_PACKAGE_NAME = "test_package";
private static final String TEST_CHANNEL = "test_channel";
private static final String TEST_CHANNEL_NAME = "TEST CHANNEL NAME";
private NotificationGuts mNotificationGuts;
private final INotificationManager mMockINotificationManager = mock(INotificationManager.class);
private final PackageManager mMockPackageManager = mock(PackageManager.class);
private NotificationChannel mNotificationChannel;
private final StatusBarNotification mMockStatusBarNotification =
mock(StatusBarNotification.class);
@Before
@UiThreadTest
public void setUp() throws Exception {
// Inflate the layout
final LayoutInflater layoutInflater =
LayoutInflater.from(InstrumentationRegistry.getTargetContext());
mNotificationGuts = (NotificationGuts) layoutInflater.inflate(R.layout.notification_guts,
null);
// PackageManager must return a packageInfo and applicationInfo.
final PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = TEST_PACKAGE_NAME;
when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).thenReturn(packageInfo);
final ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.uid = 1; // non-zero
when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).thenReturn(
applicationInfo);
// mMockStatusBarNotification with a test channel.
mNotificationChannel = new NotificationChannel(
TEST_CHANNEL, TEST_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
when(mMockStatusBarNotification.getNotificationChannel()).thenReturn(mNotificationChannel);
when(mMockStatusBarNotification.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
}
@Test
@UiThreadTest
public void testBindNotification_SetsTextApplicationName() throws Exception {
when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, null, null, null);
final TextView textView = (TextView) mNotificationGuts.findViewById(R.id.pkgname);
assertTrue(textView.getText().toString().contains("App Name"));
}
@Test
@UiThreadTest
public void testBindNotification_SetsTextChannelName() throws Exception {
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, null, null, null);
final TextView textView = (TextView) mNotificationGuts.findViewById(R.id.channel_name);
assertEquals(TEST_CHANNEL_NAME, textView.getText());
}
@Test
@UiThreadTest
public void testBindNotification_SetsOnClickListenerForSettings() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, (View v, int appUid) -> { latch.countDown(); },
null, null);
final TextView settingsButton =
(TextView) mNotificationGuts.findViewById(R.id.more_settings);
settingsButton.performClick();
// Verify that listener was triggered.
assertEquals(0, latch.getCount());
}
@Test
@UiThreadTest
public void testBindNotification_SetsOnClickListenerForDone() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, null,
(View v) -> { latch.countDown(); },
null);
final TextView doneButton = (TextView) mNotificationGuts.findViewById(R.id.done);
doneButton.performClick();
// Verify that listener was triggered.
assertEquals(0, latch.getCount());
}
@Test
@UiThreadTest
public void testHasImportanceChanged_DefaultsToFalse() throws Exception {
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, null, null, null);
assertFalse(mNotificationGuts.hasImportanceChanged());
}
@Test
@UiThreadTest
public void testHasImportanceChanged_ReturnsTrueAfterButtonChecked() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, null, null, null);
// Find the high button and check it.
RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
highButton.setChecked(true);
assertTrue(mNotificationGuts.hasImportanceChanged());
}
@Test
@UiThreadTest
public void testImportanceButtonCheckedBasedOnInitialImportance() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_HIGH);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, null, null, null);
RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
assertTrue(highButton.isChecked());
}
@Test
@UiThreadTest
public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception {
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, null, null, null);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@Test
@UiThreadTest
public void testDoesNotUpdateNotificationChannelAfterImportanceChanged() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, null, null, null);
RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
highButton.setChecked(true);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@Test
@UiThreadTest
public void testCloseControls_DoesNotUpdateNotificationChannelIfUnchanged() throws Exception {
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, null, null, null);
mNotificationGuts.closeControls(-1, -1, true);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@Test
@UiThreadTest
public void testCloseControls_CallsUpdateNotificationChannelIfChanged() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, null, null, null);
RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
highButton.setChecked(true);
mNotificationGuts.closeControls(-1, -1, true);
verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
eq(TEST_PACKAGE_NAME), anyInt(), eq(mNotificationChannel));
assertEquals(NotificationManager.IMPORTANCE_HIGH, mNotificationChannel.getImportance());
}
@Test
@UiThreadTest
public void testCloseControls_DoesNotUpdateNotificationChannelIfSaveFalse() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, null, null, null);
RadioButton highButton = (RadioButton) mNotificationGuts.findViewById(R.id.high_importance);
highButton.setChecked(true);
mNotificationGuts.closeControls(-1, -1, false);
verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
anyString(), anyInt(), any());
}
@Test
@UiThreadTest
public void testEnabledSwitchOnByDefault() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, null, null, null);
Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
assertTrue(enabledSwitch.isChecked());
}
@Test
@UiThreadTest
public void testEnabledSwitchVisibleByDefault() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, null, null, null);
Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
assertEquals(View.VISIBLE, enabledSwitch.getVisibility());
}
@Test
@UiThreadTest
public void testEnabledSwitchInvisibleIfNonBlockable() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, null, null, Collections.singleton(TEST_PACKAGE_NAME));
Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
assertEquals(View.INVISIBLE, enabledSwitch.getVisibility());
}
@Test
@UiThreadTest
public void testEnabledSwitchChangedCallsUpdateNotificationChannel() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, null, null, Collections.singleton(TEST_PACKAGE_NAME));
Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
enabledSwitch.setChecked(false);
mNotificationGuts.closeControls(-1, -1, true);
verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
eq(TEST_PACKAGE_NAME), anyInt(), eq(mNotificationChannel));
}
@Test
@UiThreadTest
public void testEnabledSwitchOverridesOtherButtons() throws Exception {
mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW);
mNotificationGuts.bindNotification(mMockPackageManager, mMockINotificationManager,
mMockStatusBarNotification, null, null, null);
Switch enabledSwitch = (Switch) mNotificationGuts.findViewById(R.id.channel_enabled_switch);
RadioButton lowButton = (RadioButton) mNotificationGuts.findViewById(R.id.low_importance);
lowButton.setChecked(true);
enabledSwitch.setChecked(false);
mNotificationGuts.closeControls(-1, -1, true);
assertEquals(NotificationManager.IMPORTANCE_NONE, mNotificationChannel.getImportance());
}
}

View File

@@ -1547,8 +1547,9 @@ public class NotificationManagerService extends SystemService {
@Override
public void updateNotificationChannelForPackage(String pkg, int uid,
NotificationChannel channel) {
checkCallerIsSystem();
enforceSystemOrSystemUI("Caller not system or systemui");
if (!channel.isAllowed()) {
// cancel
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channel.getId(), 0, 0, true,
UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED,
null);