Introduce quiet mode state to managed profile users

Quiet mode means the user will be free from visual and audio interruptions
from apps inside the managed profile, including notifications, widgets and
others. This CL adds the underlying state bit to users and exposes various
APIs to control and query the quiet mode state.

Bug: 22541941
Change-Id: If5f8e5a897843050e83b6ec26cb39561098f12b9
diff --git a/api/current.txt b/api/current.txt
index c017754..7d160a8 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8254,6 +8254,7 @@
     field public static final java.lang.String ACTION_APP_ERROR = "android.intent.action.APP_ERROR";
     field public static final java.lang.String ACTION_ASSIST = "android.intent.action.ASSIST";
     field public static final java.lang.String ACTION_ATTACH_DATA = "android.intent.action.ATTACH_DATA";
+    field public static final java.lang.String ACTION_AVAILABILITY_CHANGED = "android.intent.action.AVAILABILITY_CHANGED";
     field public static final java.lang.String ACTION_BATTERY_CHANGED = "android.intent.action.BATTERY_CHANGED";
     field public static final java.lang.String ACTION_BATTERY_LOW = "android.intent.action.BATTERY_LOW";
     field public static final java.lang.String ACTION_BATTERY_OKAY = "android.intent.action.BATTERY_OKAY";
@@ -8293,6 +8294,7 @@
     field public static final java.lang.String ACTION_LOCKED_BOOT_COMPLETED = "android.intent.action.LOCKED_BOOT_COMPLETED";
     field public static final java.lang.String ACTION_MAIN = "android.intent.action.MAIN";
     field public static final java.lang.String ACTION_MANAGED_PROFILE_ADDED = "android.intent.action.MANAGED_PROFILE_ADDED";
+    field public static final java.lang.String ACTION_MANAGED_PROFILE_AVAILABILITY_CHANGED = "android.intent.action.MANAGED_PROFILE_AVAILABILITY_CHANGED";
     field public static final java.lang.String ACTION_MANAGED_PROFILE_REMOVED = "android.intent.action.MANAGED_PROFILE_REMOVED";
     field public static final java.lang.String ACTION_MANAGE_NETWORK_USAGE = "android.intent.action.MANAGE_NETWORK_USAGE";
     field public static final java.lang.String ACTION_MANAGE_PACKAGE_STORAGE = "android.intent.action.MANAGE_PACKAGE_STORAGE";
@@ -8437,6 +8439,7 @@
     field public static final java.lang.String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER";
     field public static final java.lang.String EXTRA_PROCESS_TEXT = "android.intent.extra.PROCESS_TEXT";
     field public static final java.lang.String EXTRA_PROCESS_TEXT_READONLY = "android.intent.extra.PROCESS_TEXT_READONLY";
+    field public static final java.lang.String EXTRA_QUIET_MODE = "android.intent.extra.QUIET_MODE";
     field public static final java.lang.String EXTRA_REFERRER = "android.intent.extra.REFERRER";
     field public static final java.lang.String EXTRA_REFERRER_NAME = "android.intent.extra.REFERRER_NAME";
     field public static final java.lang.String EXTRA_REMOTE_INTENT_TOKEN = "android.intent.extra.remote_intent_token";
@@ -28069,6 +28072,7 @@
     method public android.os.Bundle getUserRestrictions();
     method public android.os.Bundle getUserRestrictions(android.os.UserHandle);
     method public boolean hasUserRestriction(java.lang.String);
+    method public boolean isQuietModeEnabled(android.os.UserHandle);
     method public boolean isSystemUser();
     method public boolean isUserAGoat();
     method public boolean isUserRunning(android.os.UserHandle);
@@ -64099,3 +64103,4 @@
   }
 
 }
+
diff --git a/api/system-current.txt b/api/system-current.txt
index 8128819..c8b3c0a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8510,6 +8510,7 @@
     field public static final java.lang.String ACTION_APP_ERROR = "android.intent.action.APP_ERROR";
     field public static final java.lang.String ACTION_ASSIST = "android.intent.action.ASSIST";
     field public static final java.lang.String ACTION_ATTACH_DATA = "android.intent.action.ATTACH_DATA";
+    field public static final java.lang.String ACTION_AVAILABILITY_CHANGED = "android.intent.action.AVAILABILITY_CHANGED";
     field public static final java.lang.String ACTION_BATTERY_CHANGED = "android.intent.action.BATTERY_CHANGED";
     field public static final java.lang.String ACTION_BATTERY_LOW = "android.intent.action.BATTERY_LOW";
     field public static final java.lang.String ACTION_BATTERY_OKAY = "android.intent.action.BATTERY_OKAY";
@@ -8551,6 +8552,7 @@
     field public static final java.lang.String ACTION_LOCKED_BOOT_COMPLETED = "android.intent.action.LOCKED_BOOT_COMPLETED";
     field public static final java.lang.String ACTION_MAIN = "android.intent.action.MAIN";
     field public static final java.lang.String ACTION_MANAGED_PROFILE_ADDED = "android.intent.action.MANAGED_PROFILE_ADDED";
+    field public static final java.lang.String ACTION_MANAGED_PROFILE_AVAILABILITY_CHANGED = "android.intent.action.MANAGED_PROFILE_AVAILABILITY_CHANGED";
     field public static final java.lang.String ACTION_MANAGED_PROFILE_REMOVED = "android.intent.action.MANAGED_PROFILE_REMOVED";
     field public static final java.lang.String ACTION_MANAGE_NETWORK_USAGE = "android.intent.action.MANAGE_NETWORK_USAGE";
     field public static final java.lang.String ACTION_MANAGE_PACKAGE_STORAGE = "android.intent.action.MANAGE_PACKAGE_STORAGE";
@@ -8701,6 +8703,7 @@
     field public static final java.lang.String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER";
     field public static final java.lang.String EXTRA_PROCESS_TEXT = "android.intent.extra.PROCESS_TEXT";
     field public static final java.lang.String EXTRA_PROCESS_TEXT_READONLY = "android.intent.extra.PROCESS_TEXT_READONLY";
+    field public static final java.lang.String EXTRA_QUIET_MODE = "android.intent.extra.QUIET_MODE";
     field public static final java.lang.String EXTRA_REFERRER = "android.intent.extra.REFERRER";
     field public static final java.lang.String EXTRA_REFERRER_NAME = "android.intent.extra.REFERRER_NAME";
     field public static final java.lang.String EXTRA_REMOTE_INTENT_TOKEN = "android.intent.extra.remote_intent_token";
@@ -30062,6 +30065,7 @@
     method public android.os.Bundle getUserRestrictions(android.os.UserHandle);
     method public boolean hasUserRestriction(java.lang.String);
     method public boolean isManagedProfile();
+    method public boolean isQuietModeEnabled(android.os.UserHandle);
     method public boolean isSystemUser();
     method public boolean isUserAGoat();
     method public boolean isUserRunning(android.os.UserHandle);
diff --git a/api/test-current.txt b/api/test-current.txt
index d05dba2..1aab3ae 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -8254,6 +8254,7 @@
     field public static final java.lang.String ACTION_APP_ERROR = "android.intent.action.APP_ERROR";
     field public static final java.lang.String ACTION_ASSIST = "android.intent.action.ASSIST";
     field public static final java.lang.String ACTION_ATTACH_DATA = "android.intent.action.ATTACH_DATA";
+    field public static final java.lang.String ACTION_AVAILABILITY_CHANGED = "android.intent.action.AVAILABILITY_CHANGED";
     field public static final java.lang.String ACTION_BATTERY_CHANGED = "android.intent.action.BATTERY_CHANGED";
     field public static final java.lang.String ACTION_BATTERY_LOW = "android.intent.action.BATTERY_LOW";
     field public static final java.lang.String ACTION_BATTERY_OKAY = "android.intent.action.BATTERY_OKAY";
@@ -8293,6 +8294,7 @@
     field public static final java.lang.String ACTION_LOCKED_BOOT_COMPLETED = "android.intent.action.LOCKED_BOOT_COMPLETED";
     field public static final java.lang.String ACTION_MAIN = "android.intent.action.MAIN";
     field public static final java.lang.String ACTION_MANAGED_PROFILE_ADDED = "android.intent.action.MANAGED_PROFILE_ADDED";
+    field public static final java.lang.String ACTION_MANAGED_PROFILE_AVAILABILITY_CHANGED = "android.intent.action.MANAGED_PROFILE_AVAILABILITY_CHANGED";
     field public static final java.lang.String ACTION_MANAGED_PROFILE_REMOVED = "android.intent.action.MANAGED_PROFILE_REMOVED";
     field public static final java.lang.String ACTION_MANAGE_NETWORK_USAGE = "android.intent.action.MANAGE_NETWORK_USAGE";
     field public static final java.lang.String ACTION_MANAGE_PACKAGE_STORAGE = "android.intent.action.MANAGE_PACKAGE_STORAGE";
@@ -8437,6 +8439,7 @@
     field public static final java.lang.String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER";
     field public static final java.lang.String EXTRA_PROCESS_TEXT = "android.intent.extra.PROCESS_TEXT";
     field public static final java.lang.String EXTRA_PROCESS_TEXT_READONLY = "android.intent.extra.PROCESS_TEXT_READONLY";
+    field public static final java.lang.String EXTRA_QUIET_MODE = "android.intent.extra.QUIET_MODE";
     field public static final java.lang.String EXTRA_REFERRER = "android.intent.extra.REFERRER";
     field public static final java.lang.String EXTRA_REFERRER_NAME = "android.intent.extra.REFERRER_NAME";
     field public static final java.lang.String EXTRA_REMOTE_INTENT_TOKEN = "android.intent.extra.remote_intent_token";
@@ -28069,6 +28072,7 @@
     method public android.os.Bundle getUserRestrictions();
     method public android.os.Bundle getUserRestrictions(android.os.UserHandle);
     method public boolean hasUserRestriction(java.lang.String);
+    method public boolean isQuietModeEnabled(android.os.UserHandle);
     method public boolean isSystemUser();
     method public boolean isUserAGoat();
     method public boolean isUserRunning(android.os.UserHandle);
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index b65d825..bc7620c 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2991,6 +2991,28 @@
             "android.intent.action.MANAGED_PROFILE_REMOVED";
 
     /**
+     * Broadcast sent to the primary user when an associated managed profile's availability has
+     * changed. This includes when the user toggles the profile's quiet mode, or when the profile
+     * owner suspends the profile. Carries an extra {@link #EXTRA_USER} that specifies the
+     * UserHandle of the profile. When quiet mode is changed, this broadcast will carry a boolean
+     * extra {@link #EXTRA_QUIET_MODE} indicating the new state of quiet mode. This is only sent to
+     * registered receivers, not manifest receivers.
+     */
+    public static final String ACTION_MANAGED_PROFILE_AVAILABILITY_CHANGED =
+            "android.intent.action.MANAGED_PROFILE_AVAILABILITY_CHANGED";
+
+    /**
+     * Broadcast sent to the managed profile when its availability has changed. This includes when
+     * the user toggles the profile's quiet mode, or when the profile owner suspends the profile.
+     * When quiet mode is changed, this broadcast will carry a boolean extra
+     * {@link #EXTRA_QUIET_MODE} indicating the new state of quiet mode. This is only sent to
+     * registered receivers, not manifest receivers. See also
+     * {@link #ACTION_MANAGED_PROFILE_AVAILABILITY_CHANGED}
+     */
+    public static final String ACTION_AVAILABILITY_CHANGED =
+            "android.intent.action.AVAILABILITY_CHANGED";
+
+    /**
      * Sent when the user taps on the clock widget in the system's "quick settings" area.
      */
     public static final String ACTION_QUICK_CLOCK =
@@ -4028,6 +4050,10 @@
     /** {@hide} */
     public static final String EXTRA_INDEX = "android.intent.extra.INDEX";
 
+    /**
+     * Optional boolean extra indicating whether quiet mode has been switched on or off.
+     */
+    public static final String EXTRA_QUIET_MODE = "android.intent.extra.QUIET_MODE";
     // ---------------------------------------------------------------------
     // ---------------------------------------------------------------------
     // Intent flags (see mFlags variable).
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index d7c2215..6e09595 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -76,6 +76,7 @@
      */
     public static final int FLAG_DISABLED = 0x00000040;
 
+    public static final int FLAG_QUIET_MODE = 0x00000080;
 
     public static final int NO_PROFILE_GROUP_ID = UserHandle.USER_NULL;
 
@@ -130,6 +131,9 @@
         return (flags & FLAG_DISABLED) != FLAG_DISABLED;
     }
 
+    public boolean isQuietModeEnabled() {
+        return (flags & FLAG_QUIET_MODE) == FLAG_QUIET_MODE;
+    }
     /**
      * Returns true if the user is a split system user.
      * <p>If {@link UserManager#isSplitSystemUser split system user mode} is not enabled,
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index c71d6cc..4af89c9 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -63,4 +63,6 @@
     void setDefaultGuestRestrictions(in Bundle restrictions);
     Bundle getDefaultGuestRestrictions();
     boolean markGuestForDeletion(int userHandle);
+    void setQuietModeEnabled(int userHandle, boolean enableQuietMode);
+    boolean isQuietModeEnabled(int userHandle);
 }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index c2fc5e4..4781a6c 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -1246,6 +1246,36 @@
     }
 
     /**
+     * Set quiet mode of a managed profile.
+     *
+     * @param userHandle The user handle of the profile.
+     * @param enableQuietMode Whether quiet mode should be enabled or disabled.
+     * @hide
+     */
+    public void setQuietModeEnabled(int userHandle, boolean enableQuietMode) {
+        try {
+            mService.setQuietModeEnabled(userHandle, enableQuietMode);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Could not change the profile's quiet mode", e);
+        }
+    }
+
+    /**
+     * Returns whether the given profile is in quiet mode or not.
+     *
+     * @param userHandle The user handle of the profile to be queried.
+     * @return true if the profile is in quiet mode, false otherwise.
+     */
+    public boolean isQuietModeEnabled(UserHandle userHandle) {
+        try {
+            return mService.isQuietModeEnabled(userHandle.getIdentifier());
+        } catch (RemoteException e) {
+            Log.w(TAG, "Could not query the profile's quiet mode", e);
+        }
+        return false;
+    }
+
+    /**
      * If the target user is a managed profile of the calling user or the caller
      * is itself a managed profile, then this returns a badged copy of the given
      * icon to be able to distinguish it from the original icon. For badging an
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ee493019..8f3822f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -322,6 +322,9 @@
     <protected-broadcast android:name="android.internal.policy.action.BURN_IN_PROTECTION" />
     <protected-broadcast android:name="android.app.action.SYSTEM_UPDATE_POLICY_CHANGED" />
     <protected-broadcast android:name="android.app.action.DEVICE_OWNER_CHANGED" />
+
+    <protected-broadcast android:name="android.intent.action.MANAGED_PROFILE_AVAILABILITY_CHANGED" />
+    <protected-broadcast android:name="android.intent.action.AVAILABILITY_CHANGED" />
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
     <!-- ====================================================================== -->
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index b859915..5910c10 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -475,6 +475,66 @@
                 && user.profileGroupId == profile.profileGroupId);
     }
 
+    private void broadcastProfileAvailabilityChanges(UserHandle profileHandle,
+            UserHandle parentHandle, Bundle extras) {
+        // Send intent to profile
+        Intent intent = new Intent(Intent.ACTION_AVAILABILITY_CHANGED);
+        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        intent.putExtras(extras);
+        mContext.sendBroadcastAsUser(intent, profileHandle);
+
+        // Send intent to parent
+        if (parentHandle != null) {
+            intent = new Intent(Intent.ACTION_MANAGED_PROFILE_AVAILABILITY_CHANGED);
+            intent.putExtra(Intent.EXTRA_USER, profileHandle);
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+            intent.putExtras(extras);
+            mContext.sendBroadcastAsUser(intent, parentHandle);
+        }
+    }
+
+    @Override
+    public void setQuietModeEnabled(int userHandle, boolean enableQuietMode) {
+        checkManageUsersPermission("silence profile");
+        boolean changed = false;
+        UserInfo profile, parent;
+        synchronized (mPackagesLock) {
+            synchronized (mUsersLock) {
+                profile = getUserInfoLU(userHandle);
+                parent = getProfileParentLU(userHandle);
+
+            }
+            if (profile == null || !profile.isManagedProfile()) {
+                throw new IllegalArgumentException("User " + userHandle + " is not a profile");
+            }
+            if (profile.isQuietModeEnabled() != enableQuietMode) {
+                profile.flags ^= UserInfo.FLAG_QUIET_MODE;
+                writeUserLP(profile);
+                changed = true;
+            }
+        }
+        if (changed) {
+            Bundle extras = new Bundle();
+            extras.putBoolean(Intent.EXTRA_QUIET_MODE, enableQuietMode);
+            broadcastProfileAvailabilityChanges(profile.getUserHandle(),
+                    parent != null ? parent.getUserHandle() : null, extras);
+        }
+    }
+
+    @Override
+    public boolean isQuietModeEnabled(int userHandle) {
+        synchronized (mPackagesLock) {
+            UserInfo info;
+            synchronized (mUsersLock) {
+                info = getUserInfoLU(userHandle);
+            }
+            if (info == null || !info.isManagedProfile()) {
+                throw new IllegalArgumentException("User " + userHandle + " is not a profile");
+            }
+            return info.isQuietModeEnabled();
+        }
+    }
+
     @Override
     public void setUserEnabled(int userId) {
         checkManageUsersPermission("enable user");