Add stream-level suppression to vibrate/audio services.

- Add new audio restriction layer to app-ops.  Restrictions add
additional constraints to audio operations at a stream-level.
Restrictions do not affect the persistable state, and are purely
additive: that is, they can only impose additional contstraints, not
enable something that has already been disabled.  Restrictions
also support a whitelisted set of exempt package names.

- Add new audio stream-level checks to app-ops.

- Implement a provisional OP_PLAY_AUDIO suppression to three
java entry points MediaPlayer, AudioTrack, & SoundPool.

- Enhance vibrator api to take stream information as an optional
hint - the constants correspond to AudioManager stream types.
OP_VIBRATE now supports the stream-level restriction check.

- Simplify Vibrator subclasses by adding default implementations
for two .vibrate calls.

- Migrate NoMan's zen-mode control to use the new app-ops
stream-level restriction mechanism.

Change-Id: Ifae8952647202f728cf1c73e881452660c704678
diff --git a/api/current.txt b/api/current.txt
index e72354b..139e927 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -19546,8 +19546,10 @@
   public abstract class Vibrator {
     method public abstract void cancel();
     method public abstract boolean hasVibrator();
-    method public abstract void vibrate(long);
-    method public abstract void vibrate(long[], int);
+    method public void vibrate(long);
+    method public void vibrate(long, int);
+    method public void vibrate(long[], int);
+    method public void vibrate(long[], int, int);
   }
 
   public class WorkSource implements android.os.Parcelable {
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 079cf7a..b616c1e 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -780,6 +780,25 @@
         }
     }
 
+    /**
+     * Set a non-persisted restriction on an audio operation at a stream-level.
+     * Restrictions are temporary additional constraints imposed on top of the persisted rules
+     * defined by {@link #setMode}.
+     *
+     * @param code The operation to restrict.
+     * @param stream The {@link android.media.AudioManager} stream type.
+     * @param mode The restriction mode (MODE_IGNORED,MODE_ERRORED) or MODE_ALLOWED to unrestrict.
+     * @param exceptionPackages Optional list of packages to exclude from the restriction.
+     * @hide
+     */
+    public void setRestriction(int code, int stream, int mode, String[] exceptionPackages) {
+        try {
+            final int uid = Binder.getCallingUid();
+            mService.setAudioRestriction(code, stream, uid, mode, exceptionPackages);
+        } catch (RemoteException e) {
+        }
+    }
+
     /** @hide */
     public void resetAllModes() {
         try {
@@ -1009,6 +1028,35 @@
     }
 
     /**
+     * Like {@link #checkOp} but at a stream-level for audio operations.
+     * @hide
+     */
+    public int checkAudioOp(int op, int stream, int uid, String packageName) {
+        try {
+            final int mode = mService.checkAudioOperation(op, stream, uid, packageName);
+            if (mode == MODE_ERRORED) {
+                throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
+            }
+            return mode;
+        } catch (RemoteException e) {
+        }
+        return MODE_IGNORED;
+    }
+
+    /**
+     * Like {@link #checkAudioOp} but instead of throwing a {@link SecurityException} it
+     * returns {@link #MODE_ERRORED}.
+     * @hide
+     */
+    public int checkAudioOpNoThrow(int op, int stream, int uid, String packageName) {
+        try {
+            return mService.checkAudioOperation(op, stream, uid, packageName);
+        } catch (RemoteException e) {
+        }
+        return MODE_IGNORED;
+    }
+
+    /**
      * Make note of an application performing an operation.  Note that you must pass
      * in both the uid and name of the application to be checked; this function will verify
      * that these two match, and if not, return {@link #MODE_IGNORED}.  If this call
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index e3a3830..0c0dfe9 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -853,13 +853,21 @@
             return true;
         }
 
+        /**
+         * @hide
+         */
         @Override
-        public void vibrate(long milliseconds) {
+        public void vibrate(int owningUid, String owningPackage, long milliseconds,
+                int streamHint) {
             vibrate(new long[] { 0, milliseconds}, -1);
         }
 
+        /**
+         * @hide
+         */
         @Override
-        public void vibrate(long[] pattern, int repeat) {
+        public void vibrate(int owningUid, String owningPackage, long[] pattern, int repeat,
+                int streamHint) {
             if (repeat >= pattern.length) {
                 throw new ArrayIndexOutOfBoundsException();
             }
@@ -870,22 +878,6 @@
             }
         }
 
-        /**
-         * @hide
-         */
-        @Override
-        public void vibrate(int owningUid, String owningPackage, long milliseconds) {
-            vibrate(milliseconds);
-        }
-
-        /**
-         * @hide
-         */
-        @Override
-        public void vibrate(int owningUid, String owningPackage, long[] pattern, int repeat) {
-            vibrate(pattern, repeat);
-        }
-
         @Override
         public void cancel() {
             try {
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
index 456ffb1..4854bc0 100644
--- a/core/java/android/os/IVibratorService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -20,8 +20,8 @@
 interface IVibratorService
 {
     boolean hasVibrator();
-    void vibrate(int uid, String packageName, long milliseconds, IBinder token);
-    void vibratePattern(int uid, String packageName, in long[] pattern, int repeat, IBinder token);
+    void vibrate(int uid, String packageName, long milliseconds, int streamHint, IBinder token);
+    void vibratePattern(int uid, String packageName, in long[] pattern, int repeat, int streamHint, IBinder token);
     void cancelVibrate(IBinder token);
 }
 
diff --git a/core/java/android/os/NullVibrator.java b/core/java/android/os/NullVibrator.java
index af90bdb..536da32 100644
--- a/core/java/android/os/NullVibrator.java
+++ b/core/java/android/os/NullVibrator.java
@@ -36,22 +36,11 @@
         return false;
     }
 
-    @Override
-    public void vibrate(long milliseconds) {
-    }
-
-    @Override
-    public void vibrate(long[] pattern, int repeat) {
-        if (repeat >= pattern.length) {
-            throw new ArrayIndexOutOfBoundsException();
-        }
-    }
-
     /**
      * @hide
      */
     @Override
-    public void vibrate(int owningUid, String owningPackage, long milliseconds) {
+    public void vibrate(int owningUid, String owningPackage, long milliseconds, int streamHint) {
         vibrate(milliseconds);
     }
 
@@ -59,8 +48,11 @@
      * @hide
      */
     @Override
-    public void vibrate(int owningUid, String owningPackage, long[] pattern, int repeat) {
-        vibrate(pattern, repeat);
+    public void vibrate(int owningUid, String owningPackage, long[] pattern, int repeat,
+            int streamHint) {
+        if (repeat >= pattern.length) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
     }
 
     @Override
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 700f80d..13bc4f6 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -16,7 +16,6 @@
 
 package android.os;
 
-import android.app.ActivityThread;
 import android.content.Context;
 import android.util.Log;
 
@@ -28,18 +27,16 @@
 public class SystemVibrator extends Vibrator {
     private static final String TAG = "Vibrator";
 
-    private final String mPackageName;
     private final IVibratorService mService;
     private final Binder mToken = new Binder();
 
     public SystemVibrator() {
-        mPackageName = ActivityThread.currentPackageName();
         mService = IVibratorService.Stub.asInterface(
                 ServiceManager.getService("vibrator"));
     }
 
     public SystemVibrator(Context context) {
-        mPackageName = context.getOpPackageName();
+        super(context);
         mService = IVibratorService.Stub.asInterface(
                 ServiceManager.getService("vibrator"));
     }
@@ -57,27 +54,17 @@
         return false;
     }
 
-    @Override
-    public void vibrate(long milliseconds) {
-        vibrate(Process.myUid(), mPackageName, milliseconds);
-    }
-
-    @Override
-    public void vibrate(long[] pattern, int repeat) {
-        vibrate(Process.myUid(), mPackageName, pattern, repeat);
-    }
-
     /**
      * @hide
      */
     @Override
-    public void vibrate(int owningUid, String owningPackage, long milliseconds) {
+    public void vibrate(int owningUid, String owningPackage, long milliseconds, int streamHint) {
         if (mService == null) {
             Log.w(TAG, "Failed to vibrate; no vibrator service.");
             return;
         }
         try {
-            mService.vibrate(owningUid, owningPackage, milliseconds, mToken);
+            mService.vibrate(owningUid, owningPackage, milliseconds, streamHint, mToken);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to vibrate.", e);
         }
@@ -87,7 +74,8 @@
      * @hide
      */
     @Override
-    public void vibrate(int owningUid, String owningPackage, long[] pattern, int repeat) {
+    public void vibrate(int owningUid, String owningPackage, long[] pattern, int repeat,
+            int streamHint) {
         if (mService == null) {
             Log.w(TAG, "Failed to vibrate; no vibrator service.");
             return;
@@ -97,7 +85,8 @@
         // anyway
         if (repeat < pattern.length) {
             try {
-                mService.vibratePattern(owningUid, owningPackage, pattern, repeat, mToken);
+                mService.vibratePattern(owningUid, owningPackage, pattern, repeat, streamHint,
+                        mToken);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed to vibrate.", e);
             }
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index 5d55143..53034d8 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -16,7 +16,9 @@
 
 package android.os;
 
+import android.app.ActivityThread;
 import android.content.Context;
+import android.media.AudioManager;
 
 /**
  * Class that operates the vibrator on the device.
@@ -28,10 +30,21 @@
  * {@link Context#getSystemService} with {@link Context#VIBRATOR_SERVICE} as the argument.
  */
 public abstract class Vibrator {
+
+    private final String mPackageName;
+
     /**
      * @hide to prevent subclassing from outside of the framework
      */
     public Vibrator() {
+        mPackageName = ActivityThread.currentPackageName();
+    }
+
+    /**
+     * @hide to prevent subclassing from outside of the framework
+     */
+    protected Vibrator(Context context) {
+        mPackageName = context.getOpPackageName();
     }
 
     /**
@@ -40,7 +53,7 @@
      * @return True if the hardware has a vibrator, else false.
      */
     public abstract boolean hasVibrator();
-    
+
     /**
      * Vibrate constantly for the specified period of time.
      * <p>This method requires the caller to hold the permission
@@ -48,7 +61,23 @@
      *
      * @param milliseconds The number of milliseconds to vibrate.
      */
-    public abstract void vibrate(long milliseconds);
+    public void vibrate(long milliseconds) {
+        vibrate(milliseconds, AudioManager.USE_DEFAULT_STREAM_TYPE);
+    }
+
+    /**
+     * Vibrate constantly for the specified period of time.
+     * <p>This method requires the caller to hold the permission
+     * {@link android.Manifest.permission#VIBRATE}.
+     *
+     * @param milliseconds The number of milliseconds to vibrate.
+     * @param streamHint An {@link AudioManager} stream type corresponding to the vibration type.
+     *        For example, specify {@link AudioManager.STREAM_ALARM} for alarm vibrations or
+     *        {@link AudioManager.STREAM_RING} for vibrations associated with incoming calls.
+     */
+    public void vibrate(long milliseconds, int streamHint) {
+        vibrate(Process.myUid(), mPackageName, milliseconds, streamHint);
+    }
 
     /**
      * Vibrate with a given pattern.
@@ -70,21 +99,52 @@
      * @param repeat the index into pattern at which to repeat, or -1 if
      *        you don't want to repeat.
      */
-    public abstract void vibrate(long[] pattern, int repeat);
+    public void vibrate(long[] pattern, int repeat) {
+        vibrate(pattern, repeat, AudioManager.USE_DEFAULT_STREAM_TYPE);
+    }
+
+    /**
+     * Vibrate with a given pattern.
+     *
+     * <p>
+     * Pass in an array of ints that are the durations for which to turn on or off
+     * the vibrator in milliseconds.  The first value indicates the number of milliseconds
+     * to wait before turning the vibrator on.  The next value indicates the number of milliseconds
+     * for which to keep the vibrator on before turning it off.  Subsequent values alternate
+     * between durations in milliseconds to turn the vibrator off or to turn the vibrator on.
+     * </p><p>
+     * To cause the pattern to repeat, pass the index into the pattern array at which
+     * to start the repeat, or -1 to disable repeating.
+     * </p>
+     * <p>This method requires the caller to hold the permission
+     * {@link android.Manifest.permission#VIBRATE}.
+     *
+     * @param pattern an array of longs of times for which to turn the vibrator on or off.
+     * @param repeat the index into pattern at which to repeat, or -1 if
+     *        you don't want to repeat.
+     * @param streamHint An {@link AudioManager} stream type corresponding to the vibration type.
+     *        For example, specify {@link AudioManager.STREAM_ALARM} for alarm vibrations or
+     *        {@link AudioManager.STREAM_RING} for vibrations associated with incoming calls.
+     */
+    public void vibrate(long[] pattern, int repeat, int streamHint) {
+        vibrate(Process.myUid(), mPackageName, pattern, repeat, streamHint);
+    }
 
     /**
      * @hide
-     * Like {@link #vibrate(long)}, but allowing the caller to specify that
+     * Like {@link #vibrate(long, int)}, but allowing the caller to specify that
      * the vibration is owned by someone else.
      */
-    public abstract void vibrate(int owningUid, String owningPackage, long milliseconds);
+    public abstract void vibrate(int owningUid, String owningPackage,
+            long milliseconds, int streamHint);
 
     /**
      * @hide
-     * Like {@link #vibrate(long[], int)}, but allowing the caller to specify that
+     * Like {@link #vibrate(long[], int, int)}, but allowing the caller to specify that
      * the vibration is owned by someone else.
      */
-    public abstract void vibrate(int owningUid, String owningPackage, long[] pattern, int repeat);
+    public abstract void vibrate(int owningUid, String owningPackage,
+            long[] pattern, int repeat, int streamHint);
 
     /**
      * Turn the vibrator off.
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 16c41f3..cd75010 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -36,4 +36,6 @@
     List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, in int[] ops);
     void setMode(int code, int uid, String packageName, int mode);
     void resetAllModes();
+    int checkAudioOperation(int code, int stream, int uid, String packageName);
+    void setAudioRestriction(int code, int stream, int uid, int mode, in String[] exceptionPackages);
 }
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index 46b74da..fe510f6 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -258,7 +258,7 @@
     private final boolean mUseFixedVolume;
 
     // stream names used by dumpStreamStates()
-    private final String[] STREAM_NAMES = new String[] {
+    private static final String[] STREAM_NAMES = new String[] {
             "STREAM_VOICE_CALL",
             "STREAM_SYSTEM",
             "STREAM_RING",
@@ -614,6 +614,12 @@
         pw.println(Integer.toHexString(mMuteAffectedStreams));
     }
 
+    /** @hide */
+    public static String streamToString(int stream) {
+        if (stream >= 0 && stream < STREAM_NAMES.length) return STREAM_NAMES[stream];
+        if (stream == AudioManager.USE_DEFAULT_STREAM_TYPE) return "USE_DEFAULT_STREAM_TYPE";
+        return "UNKNOWN_STREAM_" + stream;
+    }
 
     private void updateStreamVolumeAlias(boolean updateVolumes) {
         int dtmfStreamAlias;
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 5611efb..dee8705 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -23,11 +23,20 @@
 import java.nio.NioUtils;
 
 import android.annotation.IntDef;
+import android.app.ActivityThread;
+import android.app.AppOpsManager;
+import android.content.Context;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.util.Log;
 
+import com.android.internal.app.IAppOpsService;
+
 
 /**
  * The AudioTrack class manages and plays a single audio resource for Java applications.
@@ -239,7 +248,10 @@
      * Audio session ID
      */
     private int mSessionId = AudioSystem.AUDIO_SESSION_ALLOCATE;
-
+    /**
+     * Reference to the app-ops service.
+     */
+    private final IAppOpsService mAppOps;
 
     //--------------------------------
     // Used exclusively by native code
@@ -343,6 +355,9 @@
 
         audioBuffSizeCheck(bufferSizeInBytes);
 
+        IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
+        mAppOps = IAppOpsService.Stub.asInterface(b);
+
         if (sessionId < 0) {
             throw new IllegalArgumentException("Invalid audio session ID: "+sessionId);
         }
@@ -841,6 +856,9 @@
      *    {@link #ERROR_INVALID_OPERATION}
      */
     public int setStereoVolume(float leftVolume, float rightVolume) {
+        if (isRestricted()) {
+            return SUCCESS;
+        }
         if (mState == STATE_UNINITIALIZED) {
             return ERROR_INVALID_OPERATION;
         }
@@ -1014,13 +1032,25 @@
         if (mState != STATE_INITIALIZED) {
             throw new IllegalStateException("play() called on uninitialized AudioTrack.");
         }
-
+        if (isRestricted()) {
+            setVolume(0);
+        }
         synchronized(mPlayStateLock) {
             native_start();
             mPlayState = PLAYSTATE_PLAYING;
         }
     }
 
+    private boolean isRestricted() {
+        try {
+            final int mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO, mStreamType,
+                    Process.myUid(), ActivityThread.currentPackageName());
+            return mode != AppOpsManager.MODE_ALLOWED;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
     /**
      * Stops playing the audio data.
      * When used on an instance created in {@link #MODE_STREAM} mode, audio will stop playing
@@ -1296,6 +1326,9 @@
      *    {@link #ERROR_INVALID_OPERATION}
      */
     public int setAuxEffectSendLevel(float level) {
+        if (isRestricted()) {
+            return SUCCESS;
+        }
         if (mState == STATE_UNINITIALIZED) {
             return ERROR_INVALID_OPERATION;
         }
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index e20a4af..1b92410 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -16,6 +16,8 @@
 
 package android.media;
 
+import android.app.ActivityThread;
+import android.app.AppOpsManager;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -32,6 +34,8 @@
 import android.os.Parcelable;
 import android.os.Process;
 import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.util.Log;
 import android.view.Surface;
 import android.view.SurfaceHolder;
@@ -42,6 +46,8 @@
 import android.media.SubtitleController;
 import android.media.SubtitleData;
 
+import com.android.internal.app.IAppOpsService;
+
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
@@ -576,6 +582,8 @@
     private PowerManager.WakeLock mWakeLock = null;
     private boolean mScreenOnWhilePlaying;
     private boolean mStayAwake;
+    private final IAppOpsService mAppOps;
+    private int mStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;
 
     /**
      * Default constructor. Consider using one of the create() methods for
@@ -599,6 +607,8 @@
         mOutOfBandSubtitleTracks = new Vector<SubtitleTrack>();
         mOpenSubtitleSources = new Vector<InputStream>();
         mInbandSubtitleTracks = new SubtitleTrack[0];
+        IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
+        mAppOps = IAppOpsService.Stub.asInterface(b);
 
         /* Native setup requires a weak reference to our object.
          * It's easier to create it here than in C++.
@@ -1055,13 +1065,35 @@
      *
      * @throws IllegalStateException if it is called in an invalid state
      */
-    public  void start() throws IllegalStateException {
+    public void start() throws IllegalStateException {
+        if (isRestricted()) {
+            _setVolume(0, 0);
+        }
         stayAwake(true);
         _start();
     }
 
     private native void _start() throws IllegalStateException;
 
+    private boolean isRestricted() {
+        try {
+            final int mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO,
+                    getAudioStreamType(), Process.myUid(), ActivityThread.currentPackageName());
+            return mode != AppOpsManager.MODE_ALLOWED;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    private int getAudioStreamType() {
+        if (mStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
+            mStreamType = _getAudioStreamType();
+        }
+        return mStreamType;
+    }
+
+    private native int _getAudioStreamType() throws IllegalStateException;
+
     /**
      * Stops playback after playback has been stopped or paused.
      *
@@ -1402,7 +1434,12 @@
      * @param streamtype the audio stream type
      * @see android.media.AudioManager
      */
-    public native void setAudioStreamType(int streamtype);
+    public void setAudioStreamType(int streamtype) {
+        _setAudioStreamType(streamtype);
+        mStreamType = streamtype;
+    }
+
+    private native void _setAudioStreamType(int streamtype);
 
     /**
      * Sets the player to be looping or non-looping.
@@ -1435,7 +1472,14 @@
      * The single parameter form below is preferred if the channel volumes don't need
      * to be set independently.
      */
-    public native void setVolume(float leftVolume, float rightVolume);
+    public void setVolume(float leftVolume, float rightVolume) {
+        if (isRestricted()) {
+            return;
+        }
+        _setVolume(leftVolume, rightVolume);
+    }
+
+    private native void _setVolume(float leftVolume, float rightVolume);
 
     /**
      * Similar, excepts sets volume of all channels to same value.
@@ -1500,7 +1544,14 @@
      * 0 < x <= R -> level = 10^(72*(x-R)/20/R)
      * @param level send level scalar
      */
-    public native void setAuxEffectSendLevel(float level);
+    public void setAuxEffectSendLevel(float level) {
+        if (isRestricted()) {
+            return;
+        }
+        _setAuxEffectSendLevel(level);
+    }
+
+    private native void _setAuxEffectSendLevel(float level);
 
     /*
      * @param request Parcel destinated to the media player. The
diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java
index f1b256e..14f0c69 100644
--- a/media/java/android/media/SoundPool.java
+++ b/media/java/android/media/SoundPool.java
@@ -20,16 +20,24 @@
 import java.io.FileDescriptor;
 import java.lang.ref.WeakReference;
 
+import android.app.ActivityThread;
+import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.res.AssetFileDescriptor;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemProperties;
 import android.util.AndroidRuntimeException;
 import android.util.Log;
 
+import com.android.internal.app.IAppOpsService;
+
 
 /**
  * The SoundPool class manages and plays audio resources for applications.
@@ -449,6 +457,8 @@
         private SoundPool mProxy;
 
         private final Object mLock;
+        private final int mStreamType;
+        private final IAppOpsService mAppOps;
 
         // SoundPool messages
         //
@@ -463,6 +473,9 @@
             }
             mLock = new Object();
             mProxy = proxy;
+            mStreamType = streamType;
+            IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
+            mAppOps = IAppOpsService.Stub.asInterface(b);
         }
 
         public int load(String path, int priority)
@@ -522,9 +535,27 @@
 
         public native final boolean unload(int soundID);
 
-        public native final int play(int soundID, float leftVolume, float rightVolume,
+        public final int play(int soundID, float leftVolume, float rightVolume,
+                int priority, int loop, float rate) {
+            if (isRestricted()) {
+                leftVolume = rightVolume = 0;
+            }
+            return _play(soundID, leftVolume, rightVolume, priority, loop, rate);
+        }
+
+        public native final int _play(int soundID, float leftVolume, float rightVolume,
                 int priority, int loop, float rate);
 
+        private boolean isRestricted() {
+            try {
+                final int mode = mAppOps.checkAudioOperation(AppOpsManager.OP_PLAY_AUDIO,
+                        mStreamType, Process.myUid(), ActivityThread.currentPackageName());
+                return mode != AppOpsManager.MODE_ALLOWED;
+            } catch (RemoteException e) {
+                return false;
+            }
+        }
+
         public native final void pause(int streamID);
 
         public native final void resume(int streamID);
@@ -535,8 +566,14 @@
 
         public native final void stop(int streamID);
 
-        public native final void setVolume(int streamID,
-                float leftVolume, float rightVolume);
+        public final void setVolume(int streamID, float leftVolume, float rightVolume) {
+            if (isRestricted()) {
+                return;
+            }
+            _setVolume(streamID, leftVolume, rightVolume);
+        }
+
+        private native final void _setVolume(int streamID, float leftVolume, float rightVolume);
 
         public void setVolume(int streamID, float volume) {
             setVolume(streamID, volume, volume);
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index dc3ae5b..abebd48 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -500,6 +500,20 @@
     process_media_player_call( env, thiz, mp->setAudioStreamType((audio_stream_type_t) streamtype) , NULL, NULL );
 }
 
+static jint
+android_media_MediaPlayer_getAudioStreamType(JNIEnv *env, jobject thiz)
+{
+    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
+    if (mp == NULL ) {
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return 0;
+    }
+    audio_stream_type_t streamtype;
+    process_media_player_call( env, thiz, mp->getAudioStreamType(&streamtype), NULL, NULL );
+    ALOGV("getAudioStreamType: %d (streamtype)", streamtype);
+    return (jint) streamtype;
+}
+
 static void
 android_media_MediaPlayer_setLooping(JNIEnv *env, jobject thiz, jboolean looping)
 {
@@ -841,10 +855,11 @@
     {"getDuration",         "()I",                              (void *)android_media_MediaPlayer_getDuration},
     {"_release",            "()V",                              (void *)android_media_MediaPlayer_release},
     {"_reset",              "()V",                              (void *)android_media_MediaPlayer_reset},
-    {"setAudioStreamType",  "(I)V",                             (void *)android_media_MediaPlayer_setAudioStreamType},
+    {"_setAudioStreamType", "(I)V",                             (void *)android_media_MediaPlayer_setAudioStreamType},
+    {"_getAudioStreamType", "()I",                              (void *)android_media_MediaPlayer_getAudioStreamType},
     {"setLooping",          "(Z)V",                             (void *)android_media_MediaPlayer_setLooping},
     {"isLooping",           "()Z",                              (void *)android_media_MediaPlayer_isLooping},
-    {"setVolume",           "(FF)V",                            (void *)android_media_MediaPlayer_setVolume},
+    {"_setVolume",          "(FF)V",                            (void *)android_media_MediaPlayer_setVolume},
     {"native_invoke",       "(Landroid/os/Parcel;Landroid/os/Parcel;)I",(void *)android_media_MediaPlayer_invoke},
     {"native_setMetadataFilter", "(Landroid/os/Parcel;)I",      (void *)android_media_MediaPlayer_setMetadataFilter},
     {"native_getMetadata", "(ZZLandroid/os/Parcel;)Z",          (void *)android_media_MediaPlayer_getMetadata},
@@ -853,7 +868,7 @@
     {"native_finalize",     "()V",                              (void *)android_media_MediaPlayer_native_finalize},
     {"getAudioSessionId",   "()I",                              (void *)android_media_MediaPlayer_get_audio_session_id},
     {"setAudioSessionId",   "(I)V",                             (void *)android_media_MediaPlayer_set_audio_session_id},
-    {"setAuxEffectSendLevel", "(F)V",                           (void *)android_media_MediaPlayer_setAuxEffectSendLevel},
+    {"_setAuxEffectSendLevel", "(F)V",                          (void *)android_media_MediaPlayer_setAuxEffectSendLevel},
     {"attachAuxEffect",     "(I)V",                             (void *)android_media_MediaPlayer_attachAuxEffect},
     {"native_pullBatteryData", "(Landroid/os/Parcel;)I",        (void *)android_media_MediaPlayer_pullBatteryData},
     {"native_setRetransmitEndpoint", "(Ljava/lang/String;I)I",  (void *)android_media_MediaPlayer_setRetransmitEndpoint},
diff --git a/media/jni/soundpool/android_media_SoundPool_SoundPoolImpl.cpp b/media/jni/soundpool/android_media_SoundPool_SoundPoolImpl.cpp
index 9cc55ab..bda3b6b 100644
--- a/media/jni/soundpool/android_media_SoundPool_SoundPoolImpl.cpp
+++ b/media/jni/soundpool/android_media_SoundPool_SoundPoolImpl.cpp
@@ -229,7 +229,7 @@
         "(I)Z",
         (void *)android_media_SoundPool_SoundPoolImpl_unload
     },
-    {   "play",
+    {   "_play",
         "(IFFIIF)I",
         (void *)android_media_SoundPool_SoundPoolImpl_play
     },
@@ -253,7 +253,7 @@
         "(I)V",
         (void *)android_media_SoundPool_SoundPoolImpl_stop
     },
-    {   "setVolume",
+    {   "_setVolume",
         "(IFF)V",
         (void *)android_media_SoundPool_SoundPoolImpl_setVolume
     },
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 1cca164..afa68da 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -5083,10 +5083,10 @@
         }
         if (pattern.length == 1) {
             // One-shot vibration
-            mVibrator.vibrate(owningUid, owningPackage, pattern[0]);
+            mVibrator.vibrate(owningUid, owningPackage, pattern[0], AudioManager.STREAM_SYSTEM);
         } else {
             // Pattern vibration
-            mVibrator.vibrate(owningUid, owningPackage, pattern, -1);
+            mVibrator.vibrate(owningUid, owningPackage, pattern, -1, AudioManager.STREAM_SYSTEM);
         }
         return true;
     }
diff --git a/services/core/java/com/android/server/AppOpsService.java b/services/core/java/com/android/server/AppOpsService.java
index e5615c0..e26747c 100644
--- a/services/core/java/com/android/server/AppOpsService.java
+++ b/services/core/java/com/android/server/AppOpsService.java
@@ -33,6 +33,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.media.AudioService;
 import android.os.AsyncTask;
 import android.os.Binder;
 import android.os.Handler;
@@ -42,6 +43,7 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.AtomicFile;
 import android.util.Log;
 import android.util.Pair;
@@ -123,6 +125,8 @@
             = new ArrayMap<String, ArrayList<Callback>>();
     final ArrayMap<IBinder, Callback> mModeWatchers
             = new ArrayMap<IBinder, Callback>();
+    final SparseArray<SparseArray<Restriction>> mAudioRestrictions
+            = new SparseArray<SparseArray<Restriction>>();
 
     public final class Callback implements DeathRecipient {
         final IAppOpsCallback mCallback;
@@ -553,6 +557,58 @@
     }
 
     @Override
+    public int checkAudioOperation(int code, int stream, int uid, String packageName) {
+        synchronized (this) {
+            final int mode = checkRestrictionLocked(code, stream, uid, packageName);
+            if (mode != AppOpsManager.MODE_ALLOWED) {
+                return mode;
+            }
+        }
+        return checkOperation(code, uid, packageName);
+    }
+
+    private int checkRestrictionLocked(int code, int stream, int uid, String packageName) {
+        final SparseArray<Restriction> streamRestrictions = mAudioRestrictions.get(code);
+        if (streamRestrictions != null) {
+            final Restriction r = streamRestrictions.get(stream);
+            if (r != null && !r.exceptionPackages.contains(packageName)) {
+                return r.mode;
+            }
+        }
+        return AppOpsManager.MODE_ALLOWED;
+    }
+
+    @Override
+    public void setAudioRestriction(int code, int stream, int uid, int mode,
+            String[] exceptionPackages) {
+        verifyIncomingUid(uid);
+        verifyIncomingOp(code);
+        synchronized (this) {
+            SparseArray<Restriction> streamRestrictions = mAudioRestrictions.get(code);
+            if (streamRestrictions == null) {
+                streamRestrictions = new SparseArray<Restriction>();
+                mAudioRestrictions.put(code, streamRestrictions);
+            }
+            streamRestrictions.remove(stream);
+            if (mode != AppOpsManager.MODE_ALLOWED) {
+                final Restriction r = new Restriction();
+                r.mode = mode;
+                if (exceptionPackages != null) {
+                    final int N = exceptionPackages.length;
+                    r.exceptionPackages = new ArraySet<String>(N);
+                    for (int i = 0; i < N; i++) {
+                        final String pkg = exceptionPackages[i];
+                        if (pkg != null) {
+                            r.exceptionPackages.add(pkg.trim());
+                        }
+                    }
+                }
+                streamRestrictions.put(stream, r);
+            }
+        }
+    }
+
+    @Override
     public int checkPackage(int uid, String packageName) {
         synchronized (this) {
             if (getOpsLocked(uid, packageName, true) != null) {
@@ -1048,6 +1104,31 @@
                     }
                 }
             }
+            if (mAudioRestrictions.size() > 0) {
+                boolean printedHeader = false;
+                for (int o=0; o<mAudioRestrictions.size(); o++) {
+                    final String op = AppOpsManager.opToName(mAudioRestrictions.keyAt(o));
+                    final SparseArray<Restriction> restrictions = mAudioRestrictions.valueAt(o);
+                    for (int i=0; i<restrictions.size(); i++) {
+                        if (!printedHeader){
+                            pw.println("  Audio Restrictions:");
+                            printedHeader = true;
+                            needSep = true;
+                        }
+                        final int stream = restrictions.keyAt(i);
+                        pw.print("    "); pw.print(op);
+                        pw.print(" stream="); pw.print(AudioService.streamToString(stream));
+                        Restriction r = restrictions.valueAt(i);
+                        pw.print(": mode="); pw.println(r.mode);
+                        if (!r.exceptionPackages.isEmpty()) {
+                            pw.println("      Exceptions:");
+                            for (int j=0; j<r.exceptionPackages.size(); j++) {
+                                pw.print("        "); pw.println(r.exceptionPackages.valueAt(j));
+                            }
+                        }
+                    }
+                }
+            }
             if (needSep) {
                 pw.println();
             }
@@ -1080,4 +1161,10 @@
             }
         }
     }
+
+    private static final class Restriction {
+        private static final ArraySet<String> NO_EXCEPTIONS = new ArraySet<String>();
+        int mode;
+        ArraySet<String> exceptionPackages = NO_EXCEPTIONS;
+    }
 }
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 28eb948..52f9aa9 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -84,24 +84,27 @@
         private final long    mStartTime;
         private final long[]  mPattern;
         private final int     mRepeat;
+        private final int     mStreamHint;
         private final int     mUid;
         private final String  mPackageName;
 
-        Vibration(IBinder token, long millis, int uid, String packageName) {
-            this(token, millis, null, 0, uid, packageName);
+        Vibration(IBinder token, long millis, int streamHint, int uid, String packageName) {
+            this(token, millis, null, 0, streamHint, uid, packageName);
         }
 
-        Vibration(IBinder token, long[] pattern, int repeat, int uid, String packageName) {
-            this(token, 0, pattern, repeat, uid, packageName);
+        Vibration(IBinder token, long[] pattern, int repeat, int streamHint, int uid,
+                String packageName) {
+            this(token, 0, pattern, repeat, streamHint, uid, packageName);
         }
 
         private Vibration(IBinder token, long millis, long[] pattern,
-                int repeat, int uid, String packageName) {
+                int repeat, int streamHint, int uid, String packageName) {
             mToken = token;
             mTimeout = millis;
             mStartTime = SystemClock.uptimeMillis();
             mPattern = pattern;
             mRepeat = repeat;
+            mStreamHint = streamHint;
             mUid = uid;
             mPackageName = packageName;
         }
@@ -191,7 +194,8 @@
                 Binder.getCallingPid(), Binder.getCallingUid(), null);
     }
 
-    public void vibrate(int uid, String packageName, long milliseconds, IBinder token) {
+    public void vibrate(int uid, String packageName, long milliseconds, int streamHint,
+            IBinder token) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Requires VIBRATE permission");
@@ -207,7 +211,7 @@
             return;
         }
 
-        Vibration vib = new Vibration(token, milliseconds, uid, packageName);
+        Vibration vib = new Vibration(token, milliseconds, streamHint, uid, packageName);
 
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -233,7 +237,7 @@
     }
 
     public void vibratePattern(int uid, String packageName, long[] pattern, int repeat,
-            IBinder token) {
+            int streamHint, IBinder token) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Requires VIBRATE permission");
@@ -258,7 +262,7 @@
                 return;
             }
 
-            Vibration vib = new Vibration(token, pattern, repeat, uid, packageName);
+            Vibration vib = new Vibration(token, pattern, repeat, streamHint, uid, packageName);
             try {
                 token.linkToDeath(vib, 0);
             } catch (RemoteException e) {
@@ -342,8 +346,12 @@
     // Lock held on mVibrations
     private void startVibrationLocked(final Vibration vib) {
         try {
-            int mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
+            int mode = mAppOpsService.checkAudioOperation(AppOpsManager.OP_VIBRATE,
+                    vib.mStreamHint, vib.mUid, vib.mPackageName);
+            if (mode == AppOpsManager.MODE_ALLOWED) {
+                mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
                     AppOpsManager.OP_VIBRATE, vib.mUid, vib.mPackageName);
+            }
             if (mode != AppOpsManager.MODE_ALLOWED) {
                 if (mode == AppOpsManager.MODE_ERRORED) {
                     Slog.w(TAG, "Would be an error: vibrate from uid " + vib.mUid);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e2226aa..7c2de8b 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -206,10 +206,6 @@
     final ArrayList<NotificationScorer> mScorers = new ArrayList<NotificationScorer>();
 
     private int mZenMode;
-    private int mPreZenAlarmVolume = -1;
-    private int mPreZenRingerMode = -1;
-    private boolean mZenMutingAlarm;
-    private boolean mZenMutingRinger;
     // temporary, until we update apps to provide metadata
     private static final Set<String> CALL_PACKAGES = new HashSet<String>(Arrays.asList(
             "com.google.android.dialer",
@@ -1122,9 +1118,6 @@
         private final Uri ZEN_MODE
                 = Settings.Global.getUriFor(Settings.Global.ZEN_MODE);
 
-        private final Uri MODE_RINGER
-                = Settings.Global.getUriFor(Settings.Global.MODE_RINGER);
-
         SettingsObserver(Handler handler) {
             super(handler);
         }
@@ -1137,8 +1130,6 @@
                     false, this, UserHandle.USER_ALL);
             resolver.registerContentObserver(ZEN_MODE,
                     false, this);
-            resolver.registerContentObserver(MODE_RINGER,
-                    false, this);
             update(null);
         }
 
@@ -1162,9 +1153,6 @@
             if (ZEN_MODE.equals(uri)) {
                 updateZenMode();
             }
-            if (MODE_RINGER.equals(uri)) {
-                updateRingerMode();
-            }
         }
     }
 
@@ -1230,7 +1218,6 @@
                     Settings.Global.DEVICE_PROVISIONED, 0)) {
             mDisableNotificationAlerts = true;
         }
-        updateRingerMode();
         updateZenMode();
 
         // register for various Intents
@@ -1694,10 +1681,6 @@
             pw.println("  mVibrateNotification=" + mVibrateNotification);
             pw.println("  mDisableNotificationAlerts=" + mDisableNotificationAlerts);
             pw.println("  mZenMode=" + Settings.Global.zenModeToString(mZenMode));
-            pw.println("  mPreZenAlarmVolume=" + mPreZenAlarmVolume);
-            pw.println("  mPreZenRingerMode=" + mPreZenRingerMode);
-            pw.println("  mZenMutingAlarm=" + mZenMutingAlarm);
-            pw.println("  mZenMutingRinger=" + mZenMutingRinger);
             pw.println("  mSystemReady=" + mSystemReady);
             pw.println("  mArchive=" + mArchive.toString());
             Iterator<StatusBarNotification> iter = mArchive.descendingIterator();
@@ -2023,7 +2006,7 @@
                                         useDefaultVibrate ? mDefaultVibrationPattern
                                             : mFallbackVibrationPattern,
                                         ((notification.flags & Notification.FLAG_INSISTENT) != 0)
-                                                ? 0: -1);
+                                                ? 0: -1, notification.audioStreamType);
                                 } finally {
                                     Binder.restoreCallingIdentity(identity);
                                 }
@@ -2033,7 +2016,7 @@
                                 mVibrator.vibrate(r.sbn.getUid(), r.sbn.getBasePkg(),
                                         notification.vibrate,
                                     ((notification.flags & Notification.FLAG_INSISTENT) != 0)
-                                            ? 0: -1);
+                                            ? 0: -1, notification.audioStreamType);
                             }
                         }
                     }
@@ -2552,17 +2535,6 @@
         }
     }
 
-    private void updateRingerMode() {
-        final int ringerMode = Settings.Global.getInt(getContext().getContentResolver(),
-                Settings.Global.MODE_RINGER, -1);
-        final boolean nonSilentRingerMode = ringerMode == AudioManager.RINGER_MODE_NORMAL
-                || ringerMode == AudioManager.RINGER_MODE_VIBRATE;
-        if (mZenMode != Settings.Global.ZEN_MODE_OFF && nonSilentRingerMode) {
-            Settings.Global.putInt(getContext().getContentResolver(),
-                    Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
-        }
-    }
-
     private void updateZenMode() {
         final int mode = Settings.Global.getInt(getContext().getContentResolver(),
                 Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF);
@@ -2572,62 +2544,31 @@
                     Settings.Global.zenModeToString(mode)));
         }
         mZenMode = mode;
-        if (mAudioManager != null) {
-            // call audio
-            final boolean muteCalls = mZenMode != Settings.Global.ZEN_MODE_OFF;
-            if (muteCalls) {
-                if (!mZenMutingRinger) {
-                    if (DBG) Slog.d(TAG, "Muting STREAM_RING");
-                    mAudioManager.setStreamMute(AudioManager.STREAM_RING, true);
-                    mZenMutingRinger = true;
-                }
-                // calls vibrate if ringer mode = vibrate, so set the ringer mode as well
-                final int ringerMode = mAudioManager.getRingerMode();
-                if (ringerMode != AudioManager.RINGER_MODE_SILENT) {
-                    if (DBG) Slog.d(TAG, "Saving ringer mode of " + ringerMode);
-                    mPreZenRingerMode = ringerMode;
-                    mAudioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
-                }
-            } else {
-                if (mZenMutingRinger) {
-                    if (DBG) Slog.d(TAG, "Unmuting STREAM_RING");
-                    mAudioManager.setStreamMute(AudioManager.STREAM_RING, false);
-                    mZenMutingRinger = false;
-                }
-                if (mPreZenRingerMode != -1) {
-                    if (DBG) Slog.d(TAG, "Restoring ringer mode to " + mPreZenRingerMode);
-                    mAudioManager.setRingerMode(mPreZenRingerMode);
-                    mPreZenRingerMode = -1;
-                }
-            }
-            // alarm audio
-            final boolean muteAlarms = mZenMode == Settings.Global.ZEN_MODE_FULL;
-            if (muteAlarms) {
-                if (!mZenMutingAlarm) {
-                    if (DBG) Slog.d(TAG, "Muting STREAM_ALARM");
-                    mAudioManager.setStreamMute(AudioManager.STREAM_ALARM, true);
-                    mZenMutingAlarm = true;
-                }
-                // alarms don't simply respect mute, so set the volume as well
-                final int volume = mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM);
-                if (volume != 0) {
-                    if (DBG) Slog.d(TAG, "Saving STREAM_ALARM volume of " + volume);
-                    mPreZenAlarmVolume = volume;
-                    mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 0, 0);
-                }
-            } else {
-                if (mZenMutingAlarm) {
-                    if (DBG) Slog.d(TAG, "Unmuting STREAM_ALARM");
-                    mAudioManager.setStreamMute(AudioManager.STREAM_ALARM, false);
-                    mZenMutingAlarm = false;
-                }
-                if (mPreZenAlarmVolume != -1) {
-                    if (DBG) Slog.d(TAG, "Restoring STREAM_ALARM volume to " + mPreZenAlarmVolume);
-                    mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, mPreZenAlarmVolume, 0);
-                    mPreZenAlarmVolume = -1;
-                }
-            }
-        }
+
+        final String[] exceptionPackages = null; // none (for now)
+
+        // call restrictions
+        final boolean muteCalls = mZenMode != Settings.Global.ZEN_MODE_OFF;
+        mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, AudioManager.STREAM_RING,
+                muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
+                exceptionPackages);
+        mAppOps.setRestriction(AppOpsManager.OP_PLAY_AUDIO, AudioManager.STREAM_RING,
+                muteCalls ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
+                exceptionPackages);
+
+        // alarm restrictions
+        final boolean muteAlarms = mZenMode == Settings.Global.ZEN_MODE_FULL;
+        mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, AudioManager.STREAM_ALARM,
+                muteAlarms ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
+                exceptionPackages);
+        mAppOps.setRestriction(AppOpsManager.OP_PLAY_AUDIO, AudioManager.STREAM_ALARM,
+                muteAlarms ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
+                exceptionPackages);
+
+        // restrict vibrations with no hints
+        mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, AudioManager.USE_DEFAULT_STREAM_TYPE,
+                (muteAlarms || muteCalls) ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
+                exceptionPackages);
     }
 
     private void updateRelatedUserCache(Context context) {