DO NOT MERGE resolve merge conflicts of 3604fffbff to rvc-dev-plus-aosp am: de9b76db71
Change-Id: I5c2afb88f6c3360fa90708fb90ef67a9addf433f
This commit is contained in:
@@ -1,446 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.systemui.statusbar.notification;
|
||||
|
||||
import static com.android.systemui.statusbar.StatusBarState.SHADE;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.database.ContentObserver;
|
||||
import android.hardware.display.AmbientDisplayConfiguration;
|
||||
import android.os.Build;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.service.dreams.DreamService;
|
||||
import android.service.dreams.IDreamManager;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.systemui.Dependency;
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.statusbar.NotificationPresenter;
|
||||
import com.android.systemui.statusbar.StatusBarState;
|
||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||
import com.android.systemui.statusbar.policy.BatteryController;
|
||||
import com.android.systemui.statusbar.policy.HeadsUpManager;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/**
|
||||
* Provides heads-up and pulsing state for notification entries.
|
||||
*/
|
||||
@Singleton
|
||||
public class NotificationInterruptionStateProvider {
|
||||
|
||||
private static final String TAG = "InterruptionStateProvider";
|
||||
private static final boolean DEBUG = false;
|
||||
private static final boolean DEBUG_HEADS_UP = Build.IS_DEBUGGABLE;
|
||||
private static final boolean ENABLE_HEADS_UP = true;
|
||||
private static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
|
||||
|
||||
private final StatusBarStateController mStatusBarStateController;
|
||||
private final NotificationFilter mNotificationFilter;
|
||||
private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
|
||||
|
||||
private final Context mContext;
|
||||
private final PowerManager mPowerManager;
|
||||
private final IDreamManager mDreamManager;
|
||||
private final BatteryController mBatteryController;
|
||||
|
||||
private NotificationPresenter mPresenter;
|
||||
private HeadsUpManager mHeadsUpManager;
|
||||
private HeadsUpSuppressor mHeadsUpSuppressor;
|
||||
|
||||
private ContentObserver mHeadsUpObserver;
|
||||
@VisibleForTesting
|
||||
protected boolean mUseHeadsUp = false;
|
||||
private boolean mDisableNotificationAlerts;
|
||||
|
||||
@Inject
|
||||
public NotificationInterruptionStateProvider(Context context, NotificationFilter filter,
|
||||
StatusBarStateController stateController, BatteryController batteryController) {
|
||||
this(context,
|
||||
(PowerManager) context.getSystemService(Context.POWER_SERVICE),
|
||||
IDreamManager.Stub.asInterface(
|
||||
ServiceManager.checkService(DreamService.DREAM_SERVICE)),
|
||||
new AmbientDisplayConfiguration(context),
|
||||
filter,
|
||||
batteryController,
|
||||
stateController);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected NotificationInterruptionStateProvider(
|
||||
Context context,
|
||||
PowerManager powerManager,
|
||||
IDreamManager dreamManager,
|
||||
AmbientDisplayConfiguration ambientDisplayConfiguration,
|
||||
NotificationFilter notificationFilter,
|
||||
BatteryController batteryController,
|
||||
StatusBarStateController statusBarStateController) {
|
||||
mContext = context;
|
||||
mPowerManager = powerManager;
|
||||
mDreamManager = dreamManager;
|
||||
mBatteryController = batteryController;
|
||||
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
|
||||
mNotificationFilter = notificationFilter;
|
||||
mStatusBarStateController = statusBarStateController;
|
||||
}
|
||||
|
||||
/** Sets up late-binding dependencies for this component. */
|
||||
public void setUpWithPresenter(
|
||||
NotificationPresenter notificationPresenter,
|
||||
HeadsUpManager headsUpManager,
|
||||
HeadsUpSuppressor headsUpSuppressor) {
|
||||
setUpWithPresenter(notificationPresenter, headsUpManager, headsUpSuppressor,
|
||||
new ContentObserver(Dependency.get(Dependency.MAIN_HANDLER)) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
boolean wasUsing = mUseHeadsUp;
|
||||
mUseHeadsUp = ENABLE_HEADS_UP && !mDisableNotificationAlerts
|
||||
&& Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
|
||||
mContext.getContentResolver(),
|
||||
Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
|
||||
Settings.Global.HEADS_UP_OFF);
|
||||
Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
|
||||
if (wasUsing != mUseHeadsUp) {
|
||||
if (!mUseHeadsUp) {
|
||||
Log.d(TAG,
|
||||
"dismissing any existing heads up notification on disable"
|
||||
+ " event");
|
||||
mHeadsUpManager.releaseAllImmediately();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Sets up late-binding dependencies for this component. */
|
||||
public void setUpWithPresenter(
|
||||
NotificationPresenter notificationPresenter,
|
||||
HeadsUpManager headsUpManager,
|
||||
HeadsUpSuppressor headsUpSuppressor,
|
||||
ContentObserver observer) {
|
||||
mPresenter = notificationPresenter;
|
||||
mHeadsUpManager = headsUpManager;
|
||||
mHeadsUpSuppressor = headsUpSuppressor;
|
||||
mHeadsUpObserver = observer;
|
||||
|
||||
if (ENABLE_HEADS_UP) {
|
||||
mContext.getContentResolver().registerContentObserver(
|
||||
Settings.Global.getUriFor(Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED),
|
||||
true,
|
||||
mHeadsUpObserver);
|
||||
mContext.getContentResolver().registerContentObserver(
|
||||
Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
|
||||
mHeadsUpObserver);
|
||||
}
|
||||
mHeadsUpObserver.onChange(true); // set up
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the notification should appear as a bubble with a fly-out on top of the screen.
|
||||
*
|
||||
* @param entry the entry to check
|
||||
* @return true if the entry should bubble up, false otherwise
|
||||
*/
|
||||
public boolean shouldBubbleUp(NotificationEntry entry) {
|
||||
final StatusBarNotification sbn = entry.getSbn();
|
||||
|
||||
if (!canAlertCommon(entry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!canAlertAwakeCommon(entry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!entry.canBubble()) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No bubble up: not allowed to bubble: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!entry.isBubble()) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No bubble up: notification " + sbn.getKey()
|
||||
+ " is bubble? " + entry.isBubble());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.getBubbleMetadata() == null
|
||||
|| (entry.getBubbleMetadata().getShortcutId() == null
|
||||
&& entry.getBubbleMetadata().getBubbleIntent() == null)) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "No bubble up: notification: " + sbn.getKey()
|
||||
+ " doesn't have valid metadata");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the notification should peek in from the top and alert the user.
|
||||
*
|
||||
* @param entry the entry to check
|
||||
* @return true if the entry should heads up, false otherwise
|
||||
*/
|
||||
public boolean shouldHeadsUp(NotificationEntry entry) {
|
||||
if (mStatusBarStateController.isDozing()) {
|
||||
return shouldHeadsUpWhenDozing(entry);
|
||||
} else {
|
||||
return shouldHeadsUpWhenAwake(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldHeadsUpWhenAwake(NotificationEntry entry) {
|
||||
StatusBarNotification sbn = entry.getSbn();
|
||||
|
||||
if (!mUseHeadsUp) {
|
||||
if (DEBUG_HEADS_UP) {
|
||||
Log.d(TAG, "No heads up: no huns");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!canAlertCommon(entry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!canAlertAwakeCommon(entry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean inShade = mStatusBarStateController.getState() == SHADE;
|
||||
if (entry.isBubble() && inShade) {
|
||||
if (DEBUG_HEADS_UP) {
|
||||
Log.d(TAG, "No heads up: in unlocked shade where notification is shown as a "
|
||||
+ "bubble: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.shouldSuppressPeek()) {
|
||||
if (DEBUG_HEADS_UP) {
|
||||
Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
|
||||
if (DEBUG_HEADS_UP) {
|
||||
Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isDreaming = false;
|
||||
try {
|
||||
isDreaming = mDreamManager.isDreaming();
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to query dream manager.", e);
|
||||
}
|
||||
boolean inUse = mPowerManager.isScreenOn() && !isDreaming;
|
||||
|
||||
if (!inUse) {
|
||||
if (DEBUG_HEADS_UP) {
|
||||
Log.d(TAG, "No heads up: not in use: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mHeadsUpSuppressor.canHeadsUp(entry, sbn)) {
|
||||
if (DEBUG_HEADS_UP) {
|
||||
Log.d(TAG, "No heads up: aborted by suppressor: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the notification should "pulse" on the user's display when the phone is
|
||||
* dozing. This displays the ambient view of the notification.
|
||||
*
|
||||
* @param entry the entry to check
|
||||
* @return true if the entry should ambient pulse, false otherwise
|
||||
*/
|
||||
private boolean shouldHeadsUpWhenDozing(NotificationEntry entry) {
|
||||
StatusBarNotification sbn = entry.getSbn();
|
||||
|
||||
if (!mAmbientDisplayConfiguration.pulseOnNotificationEnabled(UserHandle.USER_CURRENT)) {
|
||||
if (DEBUG_HEADS_UP) {
|
||||
Log.d(TAG, "No pulsing: disabled by setting: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mBatteryController.isAodPowerSave()) {
|
||||
if (DEBUG_HEADS_UP) {
|
||||
Log.d(TAG, "No pulsing: disabled by battery saver: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!canAlertCommon(entry)) {
|
||||
if (DEBUG_HEADS_UP) {
|
||||
Log.d(TAG, "No pulsing: notification shouldn't alert: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.shouldSuppressAmbient()) {
|
||||
if (DEBUG_HEADS_UP) {
|
||||
Log.d(TAG, "No pulsing: ambient effect suppressed: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.getImportance() < NotificationManager.IMPORTANCE_DEFAULT) {
|
||||
if (DEBUG_HEADS_UP) {
|
||||
Log.d(TAG, "No pulsing: not important enough: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Common checks between regular & AOD heads up and bubbles.
|
||||
*
|
||||
* @param entry the entry to check
|
||||
* @return true if these checks pass, false if the notification should not alert
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public boolean canAlertCommon(NotificationEntry entry) {
|
||||
StatusBarNotification sbn = entry.getSbn();
|
||||
|
||||
if (mNotificationFilter.shouldFilterOut(entry)) {
|
||||
if (DEBUG || DEBUG_HEADS_UP) {
|
||||
Log.d(TAG, "No alerting: filtered notification: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't alert notifications that are suppressed due to group alert behavior
|
||||
if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
|
||||
if (DEBUG || DEBUG_HEADS_UP) {
|
||||
Log.d(TAG, "No alerting: suppressed due to group alert behavior");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Common checks between alerts that occur while the device is awake (heads up & bubbles).
|
||||
*
|
||||
* @param entry the entry to check
|
||||
* @return true if these checks pass, false if the notification should not alert
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public boolean canAlertAwakeCommon(NotificationEntry entry) {
|
||||
StatusBarNotification sbn = entry.getSbn();
|
||||
|
||||
if (mPresenter.isDeviceInVrMode()) {
|
||||
if (DEBUG_HEADS_UP) {
|
||||
Log.d(TAG, "No alerting: no huns or vr mode");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isSnoozedPackage(sbn)) {
|
||||
if (DEBUG_HEADS_UP) {
|
||||
Log.d(TAG, "No alerting: snoozed package: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.hasJustLaunchedFullScreenIntent()) {
|
||||
if (DEBUG_HEADS_UP) {
|
||||
Log.d(TAG, "No alerting: recent fullscreen: " + sbn.getKey());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isSnoozedPackage(StatusBarNotification sbn) {
|
||||
return mHeadsUpManager.isSnoozed(sbn.getPackageName());
|
||||
}
|
||||
|
||||
/** Sets whether to disable all alerts. */
|
||||
public void setDisableNotificationAlerts(boolean disableNotificationAlerts) {
|
||||
mDisableNotificationAlerts = disableNotificationAlerts;
|
||||
mHeadsUpObserver.onChange(true);
|
||||
}
|
||||
|
||||
/** Whether all alerts are disabled. */
|
||||
@VisibleForTesting
|
||||
public boolean areNotificationAlertsDisabled() {
|
||||
return mDisableNotificationAlerts;
|
||||
}
|
||||
|
||||
/** Whether HUNs should be used. */
|
||||
@VisibleForTesting
|
||||
public boolean getUseHeadsUp() {
|
||||
return mUseHeadsUp;
|
||||
}
|
||||
|
||||
protected NotificationPresenter getPresenter() {
|
||||
return mPresenter;
|
||||
}
|
||||
|
||||
/**
|
||||
* When an entry was added, should we launch its fullscreen intent? Examples are Alarms or
|
||||
* incoming calls.
|
||||
*
|
||||
* @param entry the entry that was added
|
||||
* @return {@code true} if we should launch the full screen intent
|
||||
*/
|
||||
public boolean shouldLaunchFullScreenIntentWhenAdded(NotificationEntry entry) {
|
||||
return entry.getSbn().getNotification().fullScreenIntent != null
|
||||
&& (!shouldHeadsUp(entry)
|
||||
|| mStatusBarStateController.getState() == StatusBarState.KEYGUARD);
|
||||
}
|
||||
|
||||
/** A component which can suppress heads-up notifications due to the overall state of the UI. */
|
||||
public interface HeadsUpSuppressor {
|
||||
/**
|
||||
* Returns false if the provided notification is ineligible for heads-up according to this
|
||||
* component.
|
||||
*
|
||||
* @param entry entry of the notification that might be heads upped
|
||||
* @param sbn notification that might be heads upped
|
||||
* @return false if the notification can not be heads upped
|
||||
*/
|
||||
boolean canHeadsUp(NotificationEntry entry, StatusBarNotification sbn);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user