Display notification effects suppressor in the volume panel.

Bug:16958514

Change-Id: I0eac173875e8af62e3c6b39001722c3fda4517de
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index e8f6818..2b97c6b 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -62,6 +62,8 @@
     void requestHintsFromListener(in INotificationListener token, int hints);
     int getHintsFromListener(in INotificationListener token);
 
+    ComponentName getEffectsSuppressor();
+
     ZenModeConfig getZenModeConfig();
     boolean setZenModeConfig(in ZenModeConfig config);
     oneway void notifyConditions(String pkg, in IConditionProvider provider, in Condition[] conditions);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index bd9363d..fc047de 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -16,7 +16,9 @@
 
 package android.app;
 
+import android.annotation.SdkConstant;
 import android.app.Notification.Builder;
+import android.content.ComponentName;
 import android.content.Context;
 import android.os.Handler;
 import android.os.IBinder;
@@ -72,6 +74,16 @@
     private static String TAG = "NotificationManager";
     private static boolean localLOGV = false;
 
+    /**
+     * Intent that is broadcast when the state of {@link #getEffectsSuppressor()} changes.
+     * This broadcast is only sent to registered receivers.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_EFFECTS_SUPPRESSOR_CHANGED
+            = "android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED";
+
     private static INotificationManager sService;
 
     /** @hide */
@@ -227,5 +239,17 @@
         }
     }
 
+    /**
+     * @hide
+     */
+    public ComponentName getEffectsSuppressor() {
+        INotificationManager service = getService();
+        try {
+            return service.getEffectsSuppressor();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
     private Context mContext;
 }
diff --git a/packages/SystemUI/res/drawable/ic_ringer_mute.xml b/packages/SystemUI/res/drawable/ic_ringer_mute.xml
new file mode 100644
index 0000000..dee6018
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_ringer_mute.xml
@@ -0,0 +1,24 @@
+<!--
+Copyright (C) 2014 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="32dp"
+        android:height="32dp"
+        android:viewportWidth="48.0"
+        android:viewportHeight="48.0">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M23.000000,44.000000c2.200000,0.000000 4.000000,-1.800000 4.000000,-4.000000l-8.000000,0.000000C19.000000,42.200001 20.799999,44.000000 23.000000,44.000000zM36.000000,21.000000c0.000000,-6.100000 -4.300000,-11.300000 -10.000000,-12.600000L26.000000,7.000000c0.000000,-1.700000 -1.300000,-3.000000 -3.000000,-3.000000c-1.700000,0.000000 -3.000000,1.300000 -3.000000,3.000000l0.000000,1.400000c-1.000000,0.200000 -2.000000,0.600000 -2.900000,1.100000L36.000000,28.400000L36.000000,21.000000zM35.500000,38.000000l4.000000,4.000000l2.500000,-2.500000L8.500000,6.000000L6.000000,8.500000l5.800000,5.800000C10.700000,16.299999 10.000000,18.600000 10.000000,21.000000l0.000000,11.000000l-4.000000,4.000000l0.000000,2.000000L35.500000,38.000000z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/volume_panel_item.xml b/packages/SystemUI/res/layout/volume_panel_item.xml
index 18b496c..85e3fb3 100644
--- a/packages/SystemUI/res/layout/volume_panel_item.xml
+++ b/packages/SystemUI/res/layout/volume_panel_item.xml
@@ -29,14 +29,30 @@
         android:background="@drawable/btn_borderless_rect"
         android:contentDescription="@null" />
 
-    <SeekBar
-        android:id="@+id/seekbar"
+    <FrameLayout
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:paddingBottom="0dp"
-        android:paddingEnd="16dp"
-        android:paddingStart="8dp"
-        android:paddingTop="0dip" />
+        android:layout_weight="1" >
 
+        <TextView
+            android:id="@+id/suppressor"
+            android:visibility="gone"
+            android:textAppearance="@style/TextAppearance.QS.VolumeSuppressor"
+            android:paddingStart="8dp"
+            android:paddingEnd="8dp"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+        <SeekBar
+            android:id="@+id/seekbar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingBottom="0dp"
+            android:paddingEnd="16dp"
+            android:paddingStart="8dp"
+            android:paddingTop="0dp" />
+
+    </FrameLayout>
 </LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 48a031a..8c1ac9f 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -141,6 +141,11 @@
         <item name="android:textColor">@color/system_accent_color</item>
     </style>
 
+    <style name="TextAppearance.QS.VolumeSuppressor">
+        <item name="android:textSize">14sp</item>
+        <item name="android:textColor">@color/qs_tile_text</item>
+    </style>
+
     <style name="TextAppearance.QS.DetailButton">
         <item name="android:textSize">14sp</item>
         <item name="android:textAllCaps">true</item>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
index 7d102ba..600b750 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.policy;
 
+import android.content.ComponentName;
 import android.service.notification.Condition;
 
 public interface ZenModeController {
@@ -29,6 +30,7 @@
     long getNextAlarm();
     void setUserId(int userId);
     boolean isZenAvailable();
+    ComponentName getEffectsSuppressor();
 
     public static class Callback {
         public void onZenChanged(int zen) {}
@@ -36,5 +38,6 @@
         public void onConditionsChanged(Condition[] conditions) {}
         public void onNextAlarmChanged() {}
         public void onZenAvailableChanged(boolean available) {}
+        public void onEffectsSupressorChanged() {}
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
index b0c8f26..415eb27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java
@@ -18,7 +18,9 @@
 
 import android.app.AlarmManager;
 import android.app.INotificationManager;
+import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -161,12 +163,23 @@
         mSetupObserver.register();
     }
 
+    @Override
+    public ComponentName getEffectsSuppressor() {
+        return NotificationManager.from(mContext).getEffectsSuppressor();
+    }
+
     private void fireNextAlarmChanged() {
         for (Callback cb : mCallbacks) {
             cb.onNextAlarmChanged();
         }
     }
 
+    private void fireEffectsSuppressorChanged() {
+        for (Callback cb : mCallbacks) {
+            cb.onEffectsSupressorChanged();
+        }
+    }
+
     private void fireZenChanged(int zen) {
         for (Callback cb : mCallbacks) {
             cb.onZenChanged(zen);
@@ -219,6 +232,9 @@
             if (AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED.equals(intent.getAction())) {
                 fireNextAlarmChanged();
             }
+            if (NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED.equals(intent.getAction())) {
+                fireEffectsSuppressorChanged();
+            }
         }
     };
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
index 8a14288..f03c5eb 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java
@@ -19,11 +19,15 @@
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnDismissListener;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -57,6 +61,7 @@
 import android.widget.ImageView;
 import android.widget.SeekBar;
 import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.TextView;
 
 import com.android.internal.R;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
@@ -109,6 +114,7 @@
     private static final int MSG_LAYOUT_DIRECTION = 12;
     private static final int MSG_ZEN_MODE_AVAILABLE_CHANGED = 13;
     private static final int MSG_USER_ACTIVITY = 14;
+    private static final int MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED = 15;
 
     // Pseudo stream type for master volume
     private static final int STREAM_MASTER = -100;
@@ -234,8 +240,10 @@
         ViewGroup group;
         ImageView icon;
         SeekBar seekbarView;
+        TextView suppressorView;
         int iconRes;
         int iconMuteRes;
+        int iconSuppressedRes;
     }
 
     // Synchronize when accessing this
@@ -613,8 +621,12 @@
                         toggle(sc);
                     }
                 });
+                sc.iconSuppressedRes = com.android.systemui.R.drawable.ic_ringer_mute;
             }
             sc.seekbarView = (SeekBar) sc.group.findViewById(com.android.systemui.R.id.seekbar);
+            sc.suppressorView =
+                    (TextView) sc.group.findViewById(com.android.systemui.R.id.suppressor);
+            sc.suppressorView.setVisibility(View.GONE);
             final int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO ||
                     streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0;
             sc.seekbarView.setMax(getStreamMaxVolume(streamType) + plusOne);
@@ -678,6 +690,40 @@
         sc.icon.setImageResource(muted ? sc.iconMuteRes : sc.iconRes);
     }
 
+    private void updateSliderSupressor(StreamControl sc) {
+        final ComponentName suppressor = isNotificationOrRing(sc.streamType)
+                ? mZenController.getEffectsSuppressor() : null;
+        if (suppressor == null) {
+            sc.seekbarView.setVisibility(View.VISIBLE);
+            sc.suppressorView.setVisibility(View.GONE);
+        } else {
+            sc.seekbarView.setVisibility(View.GONE);
+            sc.suppressorView.setVisibility(View.VISIBLE);
+            sc.suppressorView.setText(mContext.getString(com.android.systemui.R.string.muted_by,
+                    getSuppressorCaption(suppressor)));
+            sc.icon.setImageResource(sc.iconSuppressedRes);
+        }
+    }
+
+    private String getSuppressorCaption(ComponentName suppressor) {
+        final PackageManager pm = mContext.getPackageManager();
+        try {
+            final ServiceInfo info = pm.getServiceInfo(suppressor, 0);
+            if (info != null) {
+                final CharSequence seq = info.loadLabel(pm);
+                if (seq != null) {
+                    final String str = seq.toString().trim();
+                    if (str.length() > 0) {
+                        return str;
+                    }
+                }
+            }
+        } catch (Throwable e) {
+            Log.w(TAG, "Error loading suppressor caption", e);
+        }
+        return suppressor.getPackageName();
+    }
+
     /** Update the mute and progress state of a slider */
     private void updateSlider(StreamControl sc) {
         updateSliderProgress(sc, -1);
@@ -686,6 +732,7 @@
         sc.icon.setImageDrawable(null);
         updateSliderIcon(sc, muted);
         updateSliderEnabled(sc, muted, false);
+        updateSliderSupressor(sc);
     }
 
     private void updateSliderEnabled(final StreamControl sc, boolean muted, boolean fixedVolume) {
@@ -1275,7 +1322,9 @@
                 }
                 break;
             }
-            case MSG_RINGER_MODE_CHANGED: {
+
+            case MSG_RINGER_MODE_CHANGED:
+            case MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED: {
                 if (isShowing()) {
                     updateStates();
                 }
@@ -1356,9 +1405,15 @@
     };
 
     private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() {
+        @Override
         public void onZenAvailableChanged(boolean available) {
             obtainMessage(MSG_ZEN_MODE_AVAILABLE_CHANGED, available ? 1 : 0, 0).sendToTarget();
         }
+        @Override
+        public void onEffectsSupressorChanged() {
+            obtainMessage(MSG_NOTIFICATION_EFFECTS_SUPPRESSOR_CHANGED,
+                    mZenController.getEffectsSuppressor()).sendToTarget();
+        }
     };
 
     private final MediaController.Callback mMediaControllerCb = new MediaController.Callback() {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 45bd812..c390f9b 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -29,6 +29,7 @@
 import android.app.INotificationManager;
 import android.app.ITransientNotification;
 import android.app.Notification;
+import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
 import android.content.BroadcastReceiver;
@@ -56,6 +57,7 @@
 import android.os.IInterface;
 import android.os.Looper;
 import android.os.Message;
+import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -109,6 +111,7 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
+import java.util.Objects;
 
 /** {@hide} */
 public class NotificationManagerService extends SystemService {
@@ -173,6 +176,7 @@
     NotificationRecord mVibrateNotification;
 
     private final ArraySet<ManagedServiceInfo> mListenersDisablingEffects = new ArraySet<>();
+    private ComponentName mEffectsSuppressor;
     private int mListenerHints;  // right now, all hints are global
 
     // for enabling and disabling notification pulse behavior
@@ -941,6 +945,15 @@
         scheduleListenerHintsChanged(hints);
     }
 
+    private void updateEffectsSuppressorLocked() {
+        final ComponentName suppressor = !mListenersDisablingEffects.isEmpty()
+                ? mListenersDisablingEffects.valueAt(0).component : null;
+        if (Objects.equals(suppressor, mEffectsSuppressor)) return;
+        mEffectsSuppressor = suppressor;
+        getContext().sendBroadcast(new Intent(NotificationManager.ACTION_EFFECTS_SUPPRESSOR_CHANGED)
+                .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY));
+    }
+
     private final IBinder mService = new INotificationManager.Stub() {
         // Toasts
         // ============================================================================
@@ -1299,6 +1312,7 @@
                     }
                     mZenModeHelper.requestFromListener(hints);
                     updateListenerHintsLocked();
+                    updateEffectsSuppressorLocked();
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -1384,6 +1398,12 @@
 
             dumpImpl(pw, DumpFilter.parseFromArguments(args));
         }
+
+        @Override
+        public ComponentName getEffectsSuppressor() {
+            enforceSystemOrSystemUI("INotificationManager.getEffectsSuppressor");
+            return mEffectsSuppressor;
+        }
     };
 
     private String[] getActiveNotificationKeys(INotificationListener token) {