diff --git a/api/system-current.txt b/api/system-current.txt index f23c1fc7cf98b..48869eb396c8f 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -35532,6 +35532,7 @@ package android.provider { 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 810680d9dddc1..6519605dd208c 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -32831,6 +32831,7 @@ package android.provider { 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 b5c7f7b0bd8cd..0b78d6b779ae8 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1321,6 +1321,20 @@ public final class Settings { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_WEBVIEW_SETTINGS = "android.settings.WEBVIEW_SETTINGS"; + /** + * Activity Action: Show enterprise privacy section. + *

+ * Input: Nothing. + *

+ * 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 0a32e4353e1c6..b0ce2c85d04e1 100644 --- a/core/tests/coretests/src/android/provider/SettingsProviderTest.java +++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java @@ -351,6 +351,10 @@ public class SettingsProviderTest extends AndroidTestCase { 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 af1fc593ccccb..c8b3b69dce374 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -840,10 +840,10 @@ Swipe up to unlock - + This device is managed - + This device is managed by %s @@ -992,9 +992,6 @@ No notifications - - Device may be monitored - Profile may be monitored @@ -1019,15 +1016,27 @@ Disconnect VPN - - Your device is managed by %1$s.\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. + + Your device is managed by %1$s. + + + %1$s uses %2$s to manage your device. + + + Your administrator can monitor and manage settings, corporate access, apps, data associated with your device, and your device\'s location information. + + + You\'re connected to %1$s, which can monitor your network activity, including emails, apps, and websites. + + + " " + + + Learn more 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. - - Your device is managed by %1$s.\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. - Your work profile is managed by %1$s.\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. @@ -1049,9 +1058,6 @@ Your work profile is managed by %1$s. It is connected to %2$s, which can monitor your work network activity, including emails, apps, and websites.\n\nYou\'re also connected to %3$s, which can monitor your personal network activity. - - Your device is managed by %1$s.\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 %2$s, which can monitor your network activity, including emails, apps, and websites.\n\nFor more information, contact your administrator. - Device will stay locked until you manually unlock diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooter.java index ccb28e95802e0..43308def0f4ad 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.Handler; 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 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene 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 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene 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 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene 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 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene } 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 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene 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 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene } } 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 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene 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 @@ public class QSFooter implements OnClickListener, DialogInterface.OnClickListene } } + 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 e55ff70c35c2b..d8855c87210ee 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 @@ public class QSPanel extends LinearLayout implements Tunable, Callback { 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 43ced4898365d..69281b52142cd 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 @@ public interface SecurityController extends CallbackController