Disable multi-user background recording

On user switch, kill existing processes of the background user with
android.permission.RECORD_AUDIO permission. Home activity should not be
killed to avoid an expensive restart of the home launcher, when the
user switches back.

Introduced DISALLOW_RECORD_AUDIO user restriction, which is enabled for the
background user, and removed for the foreground user.

Introduced a concept of system controlled user restriction, which can only
be set by the system, rather than device administrator.

Bug: 20346194
Change-Id: Ic942fd565e80d14424230dae612965a8e229c4ef
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ab7fba5..10855e2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19951,6 +19951,14 @@
                 return token;
             }
         }
+
+        @Override
+        public ComponentName getHomeActivityForUser(int userId) {
+            synchronized (ActivityManagerService.this) {
+                ActivityRecord homeActivity = mStackSupervisor.getHomeActivityForUser(userId);
+                return homeActivity.realActivity;
+            }
+        }
     }
 
     private final class SleepTokenImpl extends SleepToken {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 8c0d6b8..cb5ba8e 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2656,6 +2656,10 @@
     }
 
     ActivityRecord getHomeActivity() {
+        return getHomeActivityForUser(UserHandle.USER_ALL);
+    }
+
+    ActivityRecord getHomeActivityForUser(int userId) {
         final ArrayList<TaskRecord> tasks = mHomeStack.getAllTasks();
         for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
             final TaskRecord task = tasks.get(taskNdx);
@@ -2663,7 +2667,8 @@
                 final ArrayList<ActivityRecord> activities = task.mActivities;
                 for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
                     final ActivityRecord r = activities.get(activityNdx);
-                    if (r.isHomeActivity()) {
+                    if (r.isHomeActivity()
+                            && ((userId == UserHandle.USER_ALL) || (r.userId == userId))) {
                         return r;
                     }
                 }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 06fba34..aa365ea 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -21,8 +21,11 @@
 import static android.media.AudioManager.RINGER_MODE_SILENT;
 import static android.media.AudioManager.RINGER_MODE_VIBRATE;
 
+import android.Manifest;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
 import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.KeyguardManager;
 import android.bluetooth.BluetoothA2dp;
@@ -37,7 +40,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
@@ -82,11 +88,13 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.Vibrator;
 import android.provider.Settings;
 import android.provider.Settings.System;
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
+import android.util.AndroidRuntimeException;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
@@ -102,6 +110,7 @@
 import com.android.internal.util.XmlUtils;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerService;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -645,6 +654,8 @@
         intentFilter.addAction(Intent.ACTION_SCREEN_ON);
         intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
         intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+        intentFilter.addAction(Intent.ACTION_USER_BACKGROUND);
+        intentFilter.addAction(Intent.ACTION_USER_FOREGROUND);
         intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
 
         intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
@@ -668,7 +679,7 @@
             setRotationForAudioSystem();
         }
 
-        context.registerReceiver(mReceiver, intentFilter);
+        context.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null);
 
         LocalServices.addService(AudioManagerInternal.class, new AudioServiceInternal());
     }
@@ -4975,10 +4986,58 @@
                         0,
                         0,
                         mStreamStates[AudioSystem.STREAM_MUSIC], 0);
+            } else if (action.equals(Intent.ACTION_USER_BACKGROUND)) {
+                // Disable audio recording for the background user/profile
+                int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                if (userId >= 0) {
+                    // TODO Kill recording streams instead of killing processes holding permission
+                    UserInfo userInfo = UserManagerService.getInstance().getUserInfo(userId);
+                    killBackgroundUserProcessesWithRecordAudioPermission(userInfo);
+                }
+                UserManagerService.getInstance().setSystemControlledUserRestriction(
+                        UserManager.DISALLOW_RECORD_AUDIO, true, userId);
+            } else if (action.equals(Intent.ACTION_USER_FOREGROUND)) {
+                // Enable audio recording for foreground user/profile
+                int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                UserManagerService.getInstance().setSystemControlledUserRestriction(
+                        UserManager.DISALLOW_RECORD_AUDIO, false, userId);
             }
         }
     } // end class AudioServiceBroadcastReceiver
 
+    private void killBackgroundUserProcessesWithRecordAudioPermission(UserInfo oldUser) {
+        PackageManager pm = mContext.getPackageManager();
+        // Find the home activity of the user. It should not be killed to avoid expensive restart,
+        // when the user switches back. For managed profiles, we should kill all recording apps
+        ComponentName homeActivityName = null;
+        if (!oldUser.isManagedProfile()) {
+            homeActivityName = LocalServices.getService(ActivityManagerInternal.class)
+                    .getHomeActivityForUser(oldUser.id);
+        }
+        final String[] permissions = { Manifest.permission.RECORD_AUDIO };
+        List<PackageInfo> packages;
+        try {
+            packages = AppGlobals.getPackageManager()
+                    .getPackagesHoldingPermissions(permissions, 0, oldUser.id).getList();
+        } catch (RemoteException e) {
+            throw new AndroidRuntimeException(e);
+        }
+        for (int j = packages.size() - 1; j >= 0; j--) {
+            PackageInfo pkg = packages.get(j);
+            if (homeActivityName != null
+                    && pkg.packageName.equals(homeActivityName.getPackageName())
+                    && pkg.applicationInfo.isSystemApp()) {
+                continue;
+            }
+            try {
+                ActivityManagerNative.getDefault().killUid(pkg.applicationInfo.uid,
+                        "killBackgroundUserProcessesWithAudioRecordPermission");
+            } catch (RemoteException e) {
+                Log.w(TAG, "Error calling killUid", e);
+            }
+        }
+    }
+
     //==========================================================================================
     // RemoteControlDisplay / RemoteControlClient / Remote info
     //==========================================================================================
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 51b26a8..8dccfdf 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -50,6 +50,8 @@
 import android.util.TimeUtils;
 import android.util.Xml;
 
+import com.google.android.collect.Sets;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.util.ArrayUtils;
@@ -70,6 +72,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 import libcore.io.IoUtils;
 
@@ -126,6 +129,10 @@
     // without first making sure that the rest of the framework is prepared for it.
     private static final int MAX_MANAGED_PROFILES = 1;
 
+    // Set of user restrictions, which can only be enforced by the system
+    private static final Set<String> SYSTEM_CONTROLLED_RESTRICTIONS = Sets.newArraySet(
+            UserManager.DISALLOW_RECORD_AUDIO);
+
     static final int WRITE_USER_MSG = 1;
     static final int WRITE_USER_DELAY = 2*1000;  // 2 seconds
 
@@ -500,7 +507,7 @@
     public boolean hasUserRestriction(String restrictionKey, int userId) {
         synchronized (mPackagesLock) {
             Bundle restrictions = mUserRestrictions.get(userId);
-            return restrictions != null ? restrictions.getBoolean(restrictionKey) : false;
+            return restrictions != null && restrictions.getBoolean(restrictionKey);
         }
     }
 
@@ -515,25 +522,59 @@
     }
 
     @Override
+    public void setUserRestriction(String key, boolean value, int userId) {
+        synchronized (mPackagesLock) {
+            if (!SYSTEM_CONTROLLED_RESTRICTIONS.contains(key)) {
+                Bundle restrictions = getUserRestrictions(userId);
+                restrictions.putBoolean(key, value);
+                setUserRestrictionsInternalLocked(restrictions, userId);
+            }
+        }
+    }
+
+    @Override
+    public void setSystemControlledUserRestriction(String key, boolean value, int userId) {
+        checkSystemOrRoot("setSystemControlledUserRestriction");
+        synchronized (mPackagesLock) {
+            Bundle restrictions = getUserRestrictions(userId);
+            restrictions.putBoolean(key, value);
+            setUserRestrictionsInternalLocked(restrictions, userId);
+        }
+    }
+
+    @Override
     public void setUserRestrictions(Bundle restrictions, int userId) {
         checkManageUsersPermission("setUserRestrictions");
         if (restrictions == null) return;
 
         synchronized (mPackagesLock) {
-            mUserRestrictions.get(userId).clear();
-            mUserRestrictions.get(userId).putAll(restrictions);
-            long token = Binder.clearCallingIdentity();
-            try {
-                mAppOpsService.setUserRestrictions(mUserRestrictions.get(userId), userId);
-            } catch (RemoteException e) {
-                Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
-            } finally {
-                Binder.restoreCallingIdentity(token);
+            final Bundle oldUserRestrictions = mUserRestrictions.get(userId);
+            // Restore the original state of system controlled restrictions from oldUserRestrictions
+            for (String key : SYSTEM_CONTROLLED_RESTRICTIONS) {
+                restrictions.remove(key);
+                if (oldUserRestrictions.containsKey(key)) {
+                    restrictions.putBoolean(key, oldUserRestrictions.getBoolean(key));
+                }
             }
-            scheduleWriteUserLocked(mUsers.get(userId));
+            setUserRestrictionsInternalLocked(restrictions, userId);
         }
     }
 
+    private void setUserRestrictionsInternalLocked(Bundle restrictions, int userId) {
+        final Bundle userRestrictions = mUserRestrictions.get(userId);
+        userRestrictions.clear();
+        userRestrictions.putAll(restrictions);
+        long token = Binder.clearCallingIdentity();
+        try {
+        mAppOpsService.setUserRestrictions(userRestrictions, userId);
+        } catch (RemoteException e) {
+            Log.w(LOG_TAG, "Unable to notify AppOpsService of UserRestrictions");
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        scheduleWriteUserLocked(mUsers.get(userId));
+    }
+
     /**
      * Check if we've hit the limit of how many users can be created.
      */
@@ -569,6 +610,13 @@
         }
     }
 
+    private static void checkSystemOrRoot(String message) {
+        final int uid = Binder.getCallingUid();
+        if (uid != Process.SYSTEM_UID && uid != 0) {
+            throw new SecurityException("Only system may call: " + message);
+        }
+    }
+
     private void writeBitmapLocked(UserInfo info, Bitmap bitmap) {
         try {
             File dir = new File(mUsersDir, Integer.toString(info.id));