Link from Quick Settings to Enterprise Privacy page

This CL updates the information shown in Quick Settings when a device
is managed by a Device Owner and adds a "learn more" link that takes
the user to the Enterprise Privacy page in Settings.

Bug: 32692748
Bug: 25779452
Test: runtest --path frameworks/base/packages/SystemUI/tests &
      runtest --path frameworks/base/core/tests/coretests

Change-Id: I8cbb6f2bb5c6da29ae581b6dcf7a01b1a4f2af2b
diff --git a/api/system-current.txt b/api/system-current.txt
index f23c1fc..48869eb 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -35532,6 +35532,7 @@
     field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
     field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
     field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
+    field public static final java.lang.String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
     field public static final java.lang.String ACTION_HARD_KEYBOARD_SETTINGS = "android.settings.HARD_KEYBOARD_SETTINGS";
     field public static final java.lang.String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
     field public static final java.lang.String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
diff --git a/api/test-current.txt b/api/test-current.txt
index 810680d..6519605 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -32831,6 +32831,7 @@
     field public static final java.lang.String ACTION_DEVICE_INFO_SETTINGS = "android.settings.DEVICE_INFO_SETTINGS";
     field public static final java.lang.String ACTION_DISPLAY_SETTINGS = "android.settings.DISPLAY_SETTINGS";
     field public static final java.lang.String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS";
+    field public static final java.lang.String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
     field public static final java.lang.String ACTION_HARD_KEYBOARD_SETTINGS = "android.settings.HARD_KEYBOARD_SETTINGS";
     field public static final java.lang.String ACTION_HOME_SETTINGS = "android.settings.HOME_SETTINGS";
     field public static final java.lang.String ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS = "android.settings.IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS";
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b5c7f7b..0b78d6b 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1321,6 +1321,20 @@
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_WEBVIEW_SETTINGS = "android.settings.WEBVIEW_SETTINGS";
 
+    /**
+     * Activity Action: Show enterprise privacy section.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_ENTERPRISE_PRIVACY_SETTINGS
+            = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
+
     // End of Intent actions for Settings
 
     /**
diff --git a/core/tests/coretests/src/android/provider/SettingsProviderTest.java b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
index 0a32e43..b0ce2c8 100644
--- a/core/tests/coretests/src/android/provider/SettingsProviderTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
@@ -351,6 +351,10 @@
         assertCanBeHandled(new Intent(Settings.ACTION_WIFI_SETTINGS));
         assertCanBeHandled(new Intent(Settings.ACTION_WIFI_SAVED_NETWORK_SETTINGS));
         assertCanBeHandled(new Intent(Settings.ACTION_WIRELESS_SETTINGS));
+
+        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)) {
+            assertCanBeHandled(new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS));
+        }
     }
 
     private void assertCanBeHandled(final Intent intent) {
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index af1fc59..c8b3b69d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -840,10 +840,10 @@
     <!-- Shows when people have pressed the unlock icon to explain how to unlock. [CHAR LIMIT=60] -->
     <string name="keyguard_unlock">Swipe up to unlock</string>
 
-    <!-- Text on keyguard screen indicating that the device is enterprise-managed by a Device Owner [CHAR LIMIT=60] -->
+    <!-- Text on keyguard screen and in Quick Settings footer indicating that the device is enterprise-managed by a Device Owner [CHAR LIMIT=60] -->
     <string name="do_disclosure_generic">This device is managed</string>
 
-    <!-- Text on keyguard screen indicating that the device is enterprise-managed by a Device Owner [CHAR LIMIT=40] -->
+    <!-- Text on keyguard screen and in Quick Settings footer indicating that the device is enterprise-managed by a Device Owner [CHAR LIMIT=40] -->
     <string name="do_disclosure_with_name">This device is managed by <xliff:g id="organization_name" example="Foo, Inc.">%s</xliff:g></string>
 
     <!-- Shows when people have clicked on the phone icon [CHAR LIMIT=60] -->
@@ -992,9 +992,6 @@
     <!-- Text which is shown in the notification shade when there are no notifications. [CHAR LIMIT=30] -->
     <string name="empty_shade_text">No notifications</string>
 
-    <!-- Footer device owned text [CHAR LIMIT=50] -->
-    <string name="device_owned_footer">Device may be monitored</string>
-
     <!-- Footer profile owned text [CHAR LIMIT=50] -->
     <string name="profile_owned_footer">Profile may be monitored</string>
 
@@ -1019,15 +1016,27 @@
     <!-- Monitoring dialog disconnect vpn button [CHAR LIMIT=30] -->
     <string name="disconnect_vpn">Disconnect VPN</string>
 
-    <!-- Monitoring dialog device owner body text [CHAR LIMIT=400] -->
-    <string name="monitoring_description_device_owned">Your device is managed by <xliff:g id="organization">%1$s</xliff:g>.\n\nYour administrator can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information. For more information, contact your administrator.</string>
+    <!-- Monitoring dialog: Header indicating that the device is managed by a Device Owner app [CHAR LIMIT=80] -->
+    <string name="monitoring_description_do_header_generic">Your device is managed by <xliff:g id="device_owner_app" example="Google Mobile Management">%1$s</xliff:g>.</string>
+
+    <!-- Monitoring dialog: Header indicating that the device is managed by a Device Owner app [CHAR LIMIT=60] -->
+    <string name="monitoring_description_do_header_with_name"><xliff:g id="organization_name" example="Foo, Inc.">%1$s</xliff:g> uses <xliff:g id="device_owner_app" example="Google Mobile Management">%2$s</xliff:g> to manage your device.</string>
+
+    <!-- Monitoring dialog: Part of text body explaining what a Device Owner app can do [CHAR LIMIT=130] -->
+    <string name="monitoring_description_do_body">Your administrator can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.</string>
+
+    <!-- Monitoring dialog: Part of text body explaining that a VPN is connected and what it can do, for devices managed by a Device Owner app [CHAR LIMIT=130] -->
+    <string name="monitoring_description_do_body_vpn">You\'re connected to <xliff:g id="vpn_app">%1$s</xliff:g>, which can monitor your network activity, including emails, apps, and websites.</string>
+
+    <!-- Monitoring dialog: Space that separates the body text and the "learn more" link that follows it. [CHAR LIMIT=5] -->
+    <string name="monitoring_description_do_learn_more_separator">" "</string>
+
+    <!-- Monitoring dialog: Link to learn more about what a Device Owner app can do [CHAR LIMIT=55] -->
+    <string name="monitoring_description_do_learn_more">Learn more</string>
 
     <!-- Monitoring dialog VPN text [CHAR LIMIT=400] -->
     <string name="monitoring_description_vpn">You gave an app permission to set up a VPN connection.\n\nThis app can monitor your device and network activity, including emails, apps, and websites.</string>
 
-    <!-- Monitoring dialog VPN with device owner text [CHAR LIMIT=400] -->
-    <string name="monitoring_description_vpn_device_owned">Your device is managed by <xliff:g id="organization">%1$s</xliff:g>.\n\nYour administrator can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.\n\nYou\'re connected to a VPN, which can monitor your network activity, including emails, apps, and websites.\n\nFor more information, contact your administrator.</string>
-
     <!-- Monitoring dialog VPN with profile owner text [CHAR LIMIT=400] -->
     <string name="monitoring_description_vpn_profile_owned">Your work profile is managed by <xliff:g id="organization">%1$s</xliff:g>.\n\nYour administrator is capable of monitoring your network activity including emails, apps, and websites.\n\nFor more information, contact your administrator.\n\nYou\'re also connected to a VPN, which can monitor your network activity.</string>
 
@@ -1049,9 +1058,6 @@
     <!-- Monitoring dialog text for multiple apps (in personal and work profiles) [CHAR LIMIT=400] -->
     <string name="monitoring_description_app_personal_work">Your work profile is managed by <xliff:g id="organization">%1$s</xliff:g>. It is connected to <xliff:g id="application_work">%2$s</xliff:g>, which can monitor your work network activity, including emails, apps, and websites.\n\nYou\'re also connected to <xliff:g id="application_personal">%3$s</xliff:g>, which can monitor your personal network activity.</string>
 
-    <!-- Monitoring dialog text for single app (with device owner) [CHAR LIMIT=400] -->
-    <string name="monitoring_description_vpn_app_device_owned">Your device is managed by <xliff:g id="organization">%1$s</xliff:g>.\n\nYour administrator can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information.\n\nYou\'re connected to <xliff:g id="application">%2$s</xliff:g>, which can monitor your network activity, including emails, apps, and websites.\n\nFor more information, contact your administrator.</string>
-
     <!-- Indication on the keyguard that appears when the user disables trust agents until the next time they unlock manually. [CHAR LIMIT=NONE] -->
     <string name="keyguard_indication_trust_disabled">Device will stay locked until you manually unlock</string>
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
index ccb28e9..43308de 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java
@@ -24,10 +24,15 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.SpannableStringBuilder;
+import android.text.method.LinkMovementMethod;
+import android.text.style.ClickableSpan;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.view.Window;
 import android.widget.ImageView;
 import android.widget.TextView;
 
@@ -52,12 +57,12 @@
     private SecurityController mSecurityController;
     private AlertDialog mDialog;
     private QSTileHost mHost;
-    private Handler mHandler;
+    protected Handler mHandler;
     private final Handler mMainHandler;
 
     private boolean mIsVisible;
     private boolean mIsIconVisible;
-    private int mFooterTextId;
+    private CharSequence mFooterTextContent = null;
     private int mFooterIconId;
 
     public QSFooter(QSPanel qsPanel, Context context) {
@@ -68,13 +73,14 @@
         mFooterIcon = (ImageView) mRootView.findViewById(R.id.footer_icon);
         mFooterIconId = R.drawable.ic_qs_vpn;
         mContext = context;
-        mMainHandler = new Handler();
+        mMainHandler = new Handler(Looper.getMainLooper());
     }
 
-    public void setHost(QSTileHost host) {
+    public void setHostEnvironment(QSTileHost host, SecurityController securityController,
+            Looper looper) {
         mHost = host;
-        mSecurityController = host.getSecurityController();
-        mHandler = new H(host.getLooper());
+        mSecurityController = securityController;
+        mHandler = new H(looper);
     }
 
     public void setListening(boolean listening) {
@@ -114,14 +120,21 @@
 
     private void handleRefreshState() {
         mIsIconVisible = mSecurityController.isVpnEnabled();
-        // If the device has device owner, show "Device may be monitored", but --
-        // TODO See b/25779452 -- device owner doesn't actually have monitoring power.
         if (mSecurityController.isDeviceManaged()) {
-            mFooterTextId = R.string.device_owned_footer;
+            final CharSequence organizationName =
+                    mSecurityController.getDeviceOwnerOrganizationName();
+            if (organizationName != null) {
+                mFooterTextContent = mContext.getResources().getString(
+                        R.string.do_disclosure_with_name, organizationName);
+            } else {
+                mFooterTextContent =
+                        mContext.getResources().getString(R.string.do_disclosure_generic);
+            }
             mIsVisible = true;
         } else {
             boolean isBranded = mSecurityController.isVpnBranded();
-            mFooterTextId = isBranded ? R.string.branded_vpn_footer : R.string.vpn_footer;
+            mFooterTextContent = mContext.getResources().getText(
+                    isBranded ? R.string.branded_vpn_footer : R.string.vpn_footer);
             // Update the VPN footer icon, if needed.
             int footerIconId = isBranded ? R.drawable.ic_qs_branded_vpn : R.drawable.ic_qs_vpn;
             if (mFooterIconId != footerIconId) {
@@ -142,23 +155,36 @@
     }
 
     private void createDialog() {
-        String deviceOwner = mSecurityController.getDeviceOwnerName();
-        String profileOwner = mSecurityController.getProfileOwnerName();
-        String primaryVpn = mSecurityController.getPrimaryVpnName();
-        String profileVpn = mSecurityController.getProfileVpnName();
-        boolean managed = mSecurityController.hasProfileOwner();
-        boolean isBranded = deviceOwner == null && mSecurityController.isVpnBranded();
+        final String deviceOwnerPackage = mSecurityController.getDeviceOwnerName();
+        final String profileOwnerPackage = mSecurityController.getProfileOwnerName();
+        final String primaryVpn = mSecurityController.getPrimaryVpnName();
+        final String profileVpn = mSecurityController.getProfileVpnName();
+        final CharSequence deviceOwnerOrganization =
+                mSecurityController.getDeviceOwnerOrganizationName();
+        boolean hasProfileOwner = mSecurityController.hasProfileOwner();
+        boolean isBranded = deviceOwnerPackage == null && mSecurityController.isVpnBranded();
 
         mDialog = new SystemUIDialog(mContext);
         if (!isBranded) {
-            mDialog.setTitle(getTitle(deviceOwner));
+            mDialog.setTitle(getTitle(deviceOwnerPackage));
         }
-        mDialog.setMessage(getMessage(deviceOwner, profileOwner, primaryVpn, profileVpn, managed,
-                isBranded));
+        mDialog.setMessage(getMessage(deviceOwnerPackage, profileOwnerPackage, primaryVpn,
+                profileVpn, deviceOwnerOrganization, hasProfileOwner, isBranded));
+
         mDialog.setButton(DialogInterface.BUTTON_POSITIVE, getPositiveButton(isBranded), this);
         if (mSecurityController.isVpnEnabled() && !mSecurityController.isVpnRestricted()) {
             mDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getSettingsButton(), this);
         }
+
+        // Make the link "learn more" clickable.
+        // TODO: Reaching into SystemUIDialog's internal View hierarchy is ugly and error-prone.
+        // This is a temporary solution. b/33126622 will introduce a custom View hierarchy for this
+        // dialog, allowing us to set the movement method on the appropriate View without any
+        // knowledge of SystemUIDialog's internals.
+        mDialog.create();
+        ((TextView) mDialog.getWindow().findViewById(com.android.internal.R.id.message))
+                .setMovementMethod(new LinkMovementMethod());
+
         mDialog.show();
     }
 
@@ -170,22 +196,35 @@
         return mContext.getString(isBranded ? android.R.string.ok : R.string.quick_settings_done);
     }
 
-    private String getMessage(String deviceOwner, String profileOwner, String primaryVpn,
-            String profileVpn, boolean primaryUserIsManaged, boolean isBranded) {
-        // Show a special warning when the device has device owner, but --
-        // TODO See b/25779452 -- device owner doesn't actually have monitoring power.
-        if (deviceOwner != null) {
-            if (primaryVpn != null) {
-                return mContext.getString(R.string.monitoring_description_vpn_app_device_owned,
-                        deviceOwner, primaryVpn);
+    protected CharSequence getMessage(String deviceOwnerPackage, String profileOwnerPackage,
+            String primaryVpn, String profileVpn, CharSequence deviceOwnerOrganization,
+            boolean hasProfileOwner, boolean isBranded) {
+        if (deviceOwnerPackage != null) {
+            final SpannableStringBuilder message = new SpannableStringBuilder();
+            if (deviceOwnerOrganization != null) {
+                message.append(mContext.getString(
+                        R.string.monitoring_description_do_header_with_name,
+                        deviceOwnerOrganization, deviceOwnerPackage));
             } else {
-                return mContext.getString(R.string.monitoring_description_device_owned,
-                        deviceOwner);
+                message.append(mContext.getString(R.string.monitoring_description_do_header_generic,
+                        deviceOwnerPackage));
             }
+            message.append("\n\n");
+            message.append(mContext.getString(R.string.monitoring_description_do_body));
+            if (primaryVpn != null) {
+                message.append("\n\n");
+                message.append(mContext.getString(R.string.monitoring_description_do_body_vpn,
+                        primaryVpn));
+            }
+            message.append(mContext.getString(
+                    R.string.monitoring_description_do_learn_more_separator));
+            message.append(mContext.getString(R.string.monitoring_description_do_learn_more),
+                    new EnterprisePrivacySpan(), 0);
+            return message;
         } else if (primaryVpn != null) {
             if (profileVpn != null) {
                 return mContext.getString(R.string.monitoring_description_app_personal_work,
-                        profileOwner, profileVpn, primaryVpn);
+                        profileOwnerPackage, profileVpn, primaryVpn);
             } else {
                 if (isBranded) {
                     return mContext.getString(R.string.branded_monitoring_description_app_personal,
@@ -197,10 +236,10 @@
             }
         } else if (profileVpn != null) {
             return mContext.getString(R.string.monitoring_description_app_work,
-                    profileOwner, profileVpn);
-        } else if (profileOwner != null && primaryUserIsManaged) {
+                    profileOwnerPackage, profileVpn);
+        } else if (profileOwnerPackage != null && hasProfileOwner) {
             return mContext.getString(R.string.monitoring_description_device_owned,
-                    profileOwner);
+                    profileOwnerPackage);
         } else {
             // No device owner, no personal VPN, no work VPN, no user owner. Why are we here?
             return null;
@@ -225,8 +264,8 @@
     private final Runnable mUpdateDisplayState = new Runnable() {
         @Override
         public void run() {
-            if (mFooterTextId != 0) {
-                mFooterText.setText(mFooterTextId);
+            if (mFooterTextContent != null) {
+                mFooterText.setText(mFooterTextContent);
             }
             mRootView.setVisibility(mIsVisible ? View.VISIBLE : View.GONE);
             mFooterIcon.setVisibility(mIsIconVisible ? View.VISIBLE : View.INVISIBLE);
@@ -267,4 +306,18 @@
         }
     }
 
+    protected class EnterprisePrivacySpan extends ClickableSpan {
+        @Override
+        public void onClick(View widget) {
+            final Intent intent = new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS);
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+            mDialog.dismiss();
+            mContext.startActivity(intent);
+        }
+
+        @Override
+        public boolean equals(Object object) {
+            return object instanceof EnterprisePrivacySpan;
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index e55ff70..d8855c8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -179,7 +179,7 @@
         mHost = host;
         mHost.addCallback(this);
         setTiles(mHost.getTiles());
-        mFooter.setHost(host);
+        mFooter.setHostEnvironment(host, host.getSecurityController(), host.getLooper());
         mCustomizePanel = customizer;
         if (mCustomizePanel != null) {
             mCustomizePanel.setHost(mHost);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
index 43ced48..69281b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityController.java
@@ -23,6 +23,7 @@
     boolean hasProfileOwner();
     String getDeviceOwnerName();
     String getProfileOwnerName();
+    CharSequence getDeviceOwnerOrganizationName();
     boolean isVpnEnabled();
     boolean isVpnRestricted();
     /** Whether the VPN app should use branded VPN iconography.  */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 07d3b59..142f21b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -130,6 +130,11 @@
     }
 
     @Override
+    public CharSequence getDeviceOwnerOrganizationName() {
+        return mDevicePolicyManager.getDeviceOwnerOrganizationName();
+    }
+
+    @Override
     public String getPrimaryVpnName() {
         VpnConfig cfg = mCurrentVpns.get(mVpnUserId);
         if (cfg != null) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java
new file mode 100644
index 0000000..1987009
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Looper;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.SpannableStringBuilder;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.policy.SecurityController;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static junit.framework.Assert.assertEquals;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class QSFooterTest extends SysuiTestCase {
+
+    private final String MANAGING_ORGANIZATION = "organization";
+    private final String DEVICE_OWNER_PACKAGE = "TestDPC";
+    private final String VPN_PACKAGE = "TestVPN";
+
+    private ViewGroup mRootView = mock(ViewGroup.class);
+    private TextView mFooterText = mock(TextView.class);
+    private QSFooter mFooter;
+    private Resources mResources;
+    private SecurityController mSecurityController = mock(SecurityController.class);
+
+    @Before
+    public void setUp() {
+        when(mRootView.findViewById(R.id.footer_text)).thenReturn(mFooterText);
+        when(mRootView.findViewById(R.id.footer_icon)).thenReturn(mock(ImageView.class));
+        final LayoutInflater layoutInflater = mock(LayoutInflater.class);
+        when(layoutInflater.inflate(eq(R.layout.quick_settings_footer), anyObject(), anyBoolean()))
+                .thenReturn(mRootView);
+        final Context context = mock(Context.class);
+        when(context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).thenReturn(layoutInflater);
+        mResources = mContext.getResources();
+        when(context.getResources()).thenReturn(mResources);
+        mFooter = new QSFooter(null, context);
+        reset(mRootView);
+        mFooter.setHostEnvironment(null, mSecurityController, Looper.getMainLooper());
+    }
+
+    @Test
+    public void testUnmanaged() {
+        when(mSecurityController.isDeviceManaged()).thenReturn(false);
+        when(mSecurityController.isVpnEnabled()).thenReturn(false);
+        when(mSecurityController.isVpnBranded()).thenReturn(false);
+        mFooter.refreshState();
+
+        waitForIdleSync(mFooter.mHandler);
+        verify(mRootView).setVisibility(View.GONE);
+        verifyNoMoreInteractions(mRootView);
+    }
+
+    @Test
+    public void testManagedNoOwnerName() {
+        when(mSecurityController.isDeviceManaged()).thenReturn(true);
+        when(mSecurityController.getDeviceOwnerOrganizationName()).thenReturn(null);
+        mFooter.refreshState();
+
+        waitForIdleSync(mFooter.mHandler);
+        verify(mFooterText).setText(mResources.getString(R.string.do_disclosure_generic));
+        verifyNoMoreInteractions(mFooterText);
+        verify(mRootView).setVisibility(View.VISIBLE);
+        verifyNoMoreInteractions(mRootView);
+    }
+
+    @Test
+    public void testManagedOwnerName() {
+        when(mSecurityController.isDeviceManaged()).thenReturn(true);
+        when(mSecurityController.getDeviceOwnerOrganizationName())
+                .thenReturn(MANAGING_ORGANIZATION);
+        mFooter.refreshState();
+
+        waitForIdleSync(mFooter.mHandler);
+        verify(mFooterText).setText(mResources.getString(R.string.do_disclosure_with_name,
+                MANAGING_ORGANIZATION));
+        verifyNoMoreInteractions(mFooterText);
+        verify(mRootView).setVisibility(View.VISIBLE);
+        verifyNoMoreInteractions(mRootView);
+    }
+
+    @Test
+    public void testGetMessageWithNoOrganizationAndNoVPN() {
+        assertEquals(getExpectedMessage(false /* hasDeviceOwnerOrganization */, false /* hasVPN */),
+                mFooter.getMessage(DEVICE_OWNER_PACKAGE,
+                        null /* profileOwnerPackage */,
+                        null /* primaryVpn */,
+                        null /* profileVpn */,
+                        null /* deviceOwnerOrganization */,
+                        false /* hasProfileOwner */,
+                        false /* isBranded */));
+    }
+
+    @Test
+    public void testGetMessageWithNoOrganizationAndVPN() {
+        assertEquals(getExpectedMessage(false /* hasDeviceOwnerOrganization */, true /* hasVPN */),
+                mFooter.getMessage(DEVICE_OWNER_PACKAGE,
+                        null /* profileOwnerPackage */,
+                        VPN_PACKAGE,
+                        null /* profileVpn */,
+                        null /* deviceOwnerOrganization */,
+                        false /* hasProfileOwner */,
+                        false /* isBranded */));
+    }
+
+    @Test
+    public void testGetMessageWithOrganizationAndNoVPN() {
+        assertEquals(getExpectedMessage(true /* hasDeviceOwnerOrganization */, false /* hasVPN */),
+                mFooter.getMessage(DEVICE_OWNER_PACKAGE,
+                        null /* profileOwnerPackage */,
+                        null /* primaryVpn */,
+                        null /* profileVpn */,
+                        MANAGING_ORGANIZATION,
+                        false /* hasProfileOwner */,
+                        false /* isBranded */));
+    }
+
+    @Test
+    public void testGetMessageWithOrganizationAndVPN() {
+        assertEquals(getExpectedMessage(true /* hasDeviceOwnerOrganization */, true /* hasVPN */),
+                mFooter.getMessage(DEVICE_OWNER_PACKAGE,
+                        null /* profileOwnerPackage */,
+                        VPN_PACKAGE,
+                        null /* profileVpn */,
+                        MANAGING_ORGANIZATION,
+                        false /* hasProfileOwner */,
+                        false /* isBranded */));
+    }
+
+    private CharSequence getExpectedMessage(boolean hasDeviceOwnerOrganization, boolean hasVPN) {
+        final SpannableStringBuilder message = new SpannableStringBuilder();
+        message.append(hasDeviceOwnerOrganization ?
+                mResources.getString(R.string.monitoring_description_do_header_with_name,
+                        MANAGING_ORGANIZATION, DEVICE_OWNER_PACKAGE) :
+                mResources.getString(R.string.monitoring_description_do_header_generic,
+                        DEVICE_OWNER_PACKAGE));
+        message.append("\n\n");
+        message.append(mResources.getString(R.string.monitoring_description_do_body));
+        if (hasVPN) {
+            message.append("\n\n");
+            message.append(mResources.getString(R.string.monitoring_description_do_body_vpn,
+                    VPN_PACKAGE));
+        }
+        message.append(mResources.getString(
+                R.string.monitoring_description_do_learn_more_separator));
+        message.append(mResources.getString(R.string.monitoring_description_do_learn_more),
+                mFooter.new EnterprisePrivacySpan(), 0);
+        return message;
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
new file mode 100644
index 0000000..9a697ee
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SecurityControllerTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.net.ConnectivityManager;
+import android.os.UserManager;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SecurityControllerTest extends SysuiTestCase {
+    private final DevicePolicyManager mDevicePolicyManager = mock(DevicePolicyManager.class);
+    private SecurityControllerImpl mSecurityController;
+
+    @Before
+    public void setUp() throws Exception {
+        final Context context = mock(Context.class);
+        when(context.getSystemService(Context.DEVICE_POLICY_SERVICE))
+                .thenReturn(mDevicePolicyManager);
+        when(context.getSystemService(Context.CONNECTIVITY_SERVICE))
+                .thenReturn(mock(ConnectivityManager.class));
+        final UserManager userManager = mock(UserManager.class);
+        when(userManager.getUserInfo(anyInt())).thenReturn(mock(UserInfo.class));
+        when(context.getSystemService(Context.USER_SERVICE))
+                .thenReturn(userManager);
+        mSecurityController = new SecurityControllerImpl(context);
+    }
+
+    @Test
+    public void testIsDeviceManaged() {
+        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(true);
+        assertTrue(mSecurityController.isDeviceManaged());
+
+        when(mDevicePolicyManager.isDeviceManaged()).thenReturn(false);
+        assertFalse(mSecurityController.isDeviceManaged());
+    }
+
+    @Test
+    public void testGetDeviceOwnerOrganizationName() {
+        when(mDevicePolicyManager.getDeviceOwnerOrganizationName()).thenReturn("organization");
+        assertEquals("organization", mSecurityController.getDeviceOwnerOrganizationName());
+    }
+}