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/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) {