diff --git a/res/values/config.xml b/res/values/config.xml
index a85636c44e0..a4a85ba6d14 100755
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -414,4 +414,7 @@
+
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6e92a3d6613..0994be4f0dd 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -11303,4 +11303,10 @@
SIM combination
+
+
+ Your work policy info
+
+ Settings managed by your IT admin
+
diff --git a/res/xml/privacy_dashboard_settings.xml b/res/xml/privacy_dashboard_settings.xml
index aa789b90424..3ac6f4233b4 100644
--- a/res/xml/privacy_dashboard_settings.xml
+++ b/res/xml/privacy_dashboard_settings.xml
@@ -29,6 +29,14 @@
android:title="@string/summary_placeholder"
settings:controller="com.android.settings.privacy.PermissionBarChartPreferenceController"/>
+
+
+
-
\ No newline at end of file
+
diff --git a/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProvider.java b/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProvider.java
index 048782ea35c..46f9b71f230 100644
--- a/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProvider.java
+++ b/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProvider.java
@@ -124,4 +124,17 @@ public interface EnterprisePrivacyFeatureProvider {
* profile (if any).
*/
int getNumberOfActiveDeviceAdminsForCurrentUserAndManagedProfile();
+
+ /**
+ * Returns {@code true} if it is possilbe to resolve an Intent to launch the "Your work policy
+ * info" page provided by the active Device Owner or Profile Owner app if it exists, {@code
+ * false} otherwise.
+ */
+ boolean hasWorkPolicyInfo();
+
+ /**
+ * Launches the Device Owner or Profile Owner's activity that displays the "Your work policy
+ * info" page. Returns {@code true} if the activity has indeed been launched.
+ */
+ boolean showWorkPolicyInfo();
}
diff --git a/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImpl.java b/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImpl.java
index 40859885b0d..d095d880385 100644
--- a/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImpl.java
+++ b/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImpl.java
@@ -21,6 +21,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.net.ConnectivityManager;
@@ -61,19 +62,7 @@ public class EnterprisePrivacyFeatureProviderImpl implements EnterprisePrivacyFe
@Override
public boolean hasDeviceOwner() {
- if (!mPm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)) {
- return false;
- }
- return mDpm.getDeviceOwnerComponentOnAnyUser() != null;
- }
-
- private int getManagedProfileUserId() {
- for (final UserInfo userInfo : mUm.getProfiles(MY_USER_ID)) {
- if (userInfo.isManagedProfile()) {
- return userInfo.id;
- }
- }
- return UserHandle.USER_NULL;
+ return getDeviceOwnerComponent() != null;
}
@Override
@@ -234,6 +223,94 @@ public class EnterprisePrivacyFeatureProviderImpl implements EnterprisePrivacyFe
return activeAdmins;
}
+ @Override
+ public boolean hasWorkPolicyInfo() {
+ return (getWorkPolicyInfoIntentDO() != null) || (getWorkPolicyInfoIntentPO() != null);
+ }
+
+ @Override
+ public boolean showWorkPolicyInfo() {
+ Intent intent = getWorkPolicyInfoIntentDO();
+ if (intent != null) {
+ mContext.startActivity(intent);
+ return true;
+ }
+
+ intent = getWorkPolicyInfoIntentPO();
+ final UserInfo userInfo = getManagedProfileUserInfo();
+ if (intent != null && userInfo != null) {
+ mContext.startActivityAsUser(intent, userInfo.getUserHandle());
+ return true;
+ }
+
+ return false;
+ }
+
+ private ComponentName getDeviceOwnerComponent() {
+ if (!mPm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN)) {
+ return null;
+ }
+ return mDpm.getDeviceOwnerComponentOnAnyUser();
+ }
+
+ private UserInfo getManagedProfileUserInfo() {
+ for (final UserInfo userInfo : mUm.getProfiles(MY_USER_ID)) {
+ if (userInfo.isManagedProfile()) {
+ return userInfo;
+ }
+ }
+ return null;
+ }
+
+ private int getManagedProfileUserId() {
+ final UserInfo userInfo = getManagedProfileUserInfo();
+ if (userInfo != null) {
+ return userInfo.id;
+ }
+ return UserHandle.USER_NULL;
+ }
+
+ private Intent getWorkPolicyInfoIntentDO() {
+ final ComponentName ownerComponent = getDeviceOwnerComponent();
+ if (ownerComponent == null) {
+ return null;
+ }
+
+ // Only search for the required action in the Device Owner's package
+ final Intent intent =
+ new Intent(mResources.getString(R.string.config_work_policy_info_intent_action))
+ .setPackage(ownerComponent.getPackageName());
+ final List activities = mPm.queryIntentActivities(intent, 0);
+ if (activities.size() != 0) {
+ return intent;
+ }
+
+ return null;
+ }
+
+ private Intent getWorkPolicyInfoIntentPO() {
+ final int userId = getManagedProfileUserId();
+ if (userId == UserHandle.USER_NULL) {
+ return null;
+ }
+
+ final ComponentName ownerComponent = mDpm.getProfileOwnerAsUser(userId);
+ if (ownerComponent == null) {
+ return null;
+ }
+
+ // Only search for the required action in the Profile Owner's package
+ final Intent intent =
+ new Intent(mResources.getString(R.string.config_work_policy_info_intent_action))
+ .setPackage(ownerComponent.getPackageName());
+ final List activities = mPm.queryIntentActivitiesAsUser(intent, 0, userId);
+ if (activities.size() != 0) {
+ return intent;
+ }
+
+ return null;
+ }
+
protected static class EnterprisePrivacySpan extends ClickableSpan {
private final Context mContext;
diff --git a/src/com/android/settings/privacy/WorkPolicyInfoPreferenceController.java b/src/com/android/settings/privacy/WorkPolicyInfoPreferenceController.java
new file mode 100644
index 00000000000..45c2c21d57b
--- /dev/null
+++ b/src/com/android/settings/privacy/WorkPolicyInfoPreferenceController.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 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.settings.privacy;
+
+import android.content.Context;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+import androidx.preference.Preference;
+
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider;
+import com.android.settings.overlay.FeatureFactory;
+
+public class WorkPolicyInfoPreferenceController extends BasePreferenceController {
+
+ private final @NonNull EnterprisePrivacyFeatureProvider mEnterpriseProvider;
+
+ public WorkPolicyInfoPreferenceController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ mEnterpriseProvider =
+ FeatureFactory.getFactory(context).getEnterprisePrivacyFeatureProvider(context);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return mEnterpriseProvider.hasWorkPolicyInfo()
+ ? AVAILABLE_UNSEARCHABLE
+ : UNSUPPORTED_ON_DEVICE;
+ }
+
+ @Override
+ public boolean handlePreferenceTreeClick(Preference preference) {
+ if (TextUtils.equals(getPreferenceKey(), preference.getKey())) {
+ mEnterpriseProvider.showWorkPolicyInfo();
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/tests/robotests/res/values/config.xml b/tests/robotests/res/values/config.xml
index 15ae89927f1..ca2c61d1990 100644
--- a/tests/robotests/res/values/config.xml
+++ b/tests/robotests/res/values/config.xml
@@ -24,4 +24,7 @@
24dp
-
\ No newline at end of file
+
+
+ ACTION_SHOW_WORK_POLICY_INFO
+
diff --git a/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImplTest.java
index 8cbaa76355f..429ede9445f 100644
--- a/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImplTest.java
+++ b/tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImplTest.java
@@ -18,15 +18,24 @@ package com.android.settings.enterprise;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.net.ConnectivityManager;
@@ -38,9 +47,12 @@ import android.text.SpannableStringBuilder;
import com.android.settings.R;
+import com.google.common.collect.ImmutableList;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
@@ -346,6 +358,111 @@ public class EnterprisePrivacyFeatureProviderImplTest {
.isEqualTo(3);
}
+ @Test
+ public void workPolicyInfo_unmanagedDevice_shouldDoNothing() {
+ // Even if we have the intent resolved, don't show it if there's no DO or PO
+ when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()).thenReturn(null);
+ addWorkPolicyInfoIntent(OWNER.getPackageName(), true, false);
+ assertThat(mProvider.hasWorkPolicyInfo()).isFalse();
+
+ assertThat(mProvider.showWorkPolicyInfo()).isFalse();
+ verify(mContext, never()).startActivity(any());
+ }
+
+ @Test
+ public void workPolicyInfo_deviceOwner_shouldResolveIntent() {
+ // If the intent is not resolved, then there's no info to show for DO
+ when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()).thenReturn(OWNER);
+ assertThat(mProvider.hasWorkPolicyInfo()).isFalse();
+ assertThat(mProvider.showWorkPolicyInfo()).isFalse();
+
+ // If the intent is resolved, then we can use it to launch the activity
+ Intent intent = addWorkPolicyInfoIntent(OWNER.getPackageName(), true, false);
+ assertThat(mProvider.hasWorkPolicyInfo()).isTrue();
+ assertThat(mProvider.showWorkPolicyInfo()).isTrue();
+ verify(mContext).startActivity(intentEquals(intent));
+ }
+
+ @Test
+ public void workPolicyInfo_profileOwner_shouldResolveIntent() {
+ when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()).thenReturn(null);
+ mProfiles.add(new UserInfo(MANAGED_PROFILE_USER_ID, "", "", UserInfo.FLAG_MANAGED_PROFILE));
+ when(mDevicePolicyManager.getProfileOwnerAsUser(MANAGED_PROFILE_USER_ID)).thenReturn(OWNER);
+
+ // If the intent is not resolved, then there's no info to show for PO
+ assertThat(mProvider.hasWorkPolicyInfo()).isFalse();
+ assertThat(mProvider.showWorkPolicyInfo()).isFalse();
+
+ // If the intent is resolved, then we can use it to launch the activity in managed profile
+ Intent intent = addWorkPolicyInfoIntent(OWNER.getPackageName(), false, true);
+ assertThat(mProvider.hasWorkPolicyInfo()).isTrue();
+ assertThat(mProvider.showWorkPolicyInfo()).isTrue();
+ verify(mContext)
+ .startActivityAsUser(
+ intentEquals(intent),
+ argThat(handle -> handle.getIdentifier() == MANAGED_PROFILE_USER_ID));
+ }
+
+ @Test
+ public void workPolicyInfo_comp_shouldUseDeviceOwnerIntent() {
+ when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()).thenReturn(OWNER);
+ mProfiles.add(new UserInfo(MANAGED_PROFILE_USER_ID, "", "", UserInfo.FLAG_MANAGED_PROFILE));
+ when(mDevicePolicyManager.getProfileOwnerAsUser(MY_USER_ID)).thenReturn(OWNER);
+
+ // If the intent is not resolved, then there's no info to show for COMP
+ assertThat(mProvider.hasWorkPolicyInfo()).isFalse();
+ assertThat(mProvider.showWorkPolicyInfo()).isFalse();
+
+ // If the intent is resolved, then we can use it to launch the activity for device owner
+ Intent intent = addWorkPolicyInfoIntent(OWNER.getPackageName(), true, true);
+ assertThat(mProvider.hasWorkPolicyInfo()).isTrue();
+ assertThat(mProvider.showWorkPolicyInfo()).isTrue();
+ verify(mContext).startActivity(intentEquals(intent));
+ }
+
+ private Intent addWorkPolicyInfoIntent(
+ String packageName, boolean deviceOwner, boolean profileOwner) {
+ Intent intent =
+ new Intent(mResources.getString(R.string.config_work_policy_info_intent_action));
+ intent.setPackage(packageName);
+ ResolveInfo resolveInfo = new ResolveInfo();
+ resolveInfo.resolvePackageName = packageName;
+ resolveInfo.activityInfo = new ActivityInfo();
+ resolveInfo.activityInfo.name = "activityName";
+ resolveInfo.activityInfo.packageName = packageName;
+
+ List activities = ImmutableList.of(resolveInfo);
+ if (deviceOwner) {
+ when(mPackageManager.queryIntentActivities(intentEquals(intent), anyInt()))
+ .thenReturn(activities);
+ }
+ if (profileOwner) {
+ when(mPackageManager.queryIntentActivitiesAsUser(
+ intentEquals(intent), anyInt(), eq(MANAGED_PROFILE_USER_ID)))
+ .thenReturn(activities);
+ }
+
+ return intent;
+ }
+
+ private static class IntentMatcher implements ArgumentMatcher {
+ private final Intent mExpectedIntent;
+
+ public IntentMatcher(Intent expectedIntent) {
+ mExpectedIntent = expectedIntent;
+ }
+
+ @Override
+ public boolean matches(Intent actualIntent) {
+ // filterEquals() compares only the action, data, type, class, and categories.
+ return actualIntent != null && mExpectedIntent.filterEquals(actualIntent);
+ }
+ }
+
+ private static Intent intentEquals(Intent intent) {
+ return argThat(new IntentMatcher(intent));
+ }
+
private void resetAndInitializePackageManager() {
reset(mPackageManager);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN))
diff --git a/tests/robotests/src/com/android/settings/privacy/WorkPolicyInfoPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/privacy/WorkPolicyInfoPreferenceControllerTest.java
new file mode 100644
index 00000000000..a92e11eb95d
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/privacy/WorkPolicyInfoPreferenceControllerTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2019 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.settings.privacy;
+
+import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE;
+import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider;
+import com.android.settings.testutils.FakeFeatureFactory;
+
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class WorkPolicyInfoPreferenceControllerTest {
+
+ private Context mContext;
+ private FakeFeatureFactory mFakeFeatureFactory;
+ private EnterprisePrivacyFeatureProvider mEnterpriseProvider;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ mFakeFeatureFactory = FakeFeatureFactory.setupForTest();
+ mEnterpriseProvider = mFakeFeatureFactory.getEnterprisePrivacyFeatureProvider(mContext);
+ }
+
+ @Test
+ public void getAvailabilityStatus_noWorkPolicyInfo_shouldReturnUnsupported() {
+ when(mEnterpriseProvider.hasWorkPolicyInfo()).thenReturn(false);
+ WorkPolicyInfoPreferenceController controller =
+ new WorkPolicyInfoPreferenceController(mContext, "test_key");
+
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
+ }
+
+ @Test
+ public void getAvailabilityStatus_haveWorkPolicyInfo_shouldReturnAvailableUnsearchable() {
+ when(mEnterpriseProvider.hasWorkPolicyInfo()).thenReturn(true);
+ WorkPolicyInfoPreferenceController controller =
+ new WorkPolicyInfoPreferenceController(mContext, "test_key");
+
+ assertThat(controller.getAvailabilityStatus()).isEqualTo(AVAILABLE_UNSEARCHABLE);
+ }
+
+ @Test
+ public void handlePreferenceTreeClick_nonMatchingKey_shouldDoNothing() {
+ when(mEnterpriseProvider.hasWorkPolicyInfo()).thenReturn(true);
+ WorkPolicyInfoPreferenceController controller =
+ new WorkPolicyInfoPreferenceController(mContext, "test_key");
+
+ final Preference pref = new Preference(mContext);
+ assertThat(controller.handlePreferenceTreeClick(pref)).isFalse();
+ verify(mEnterpriseProvider, never()).showWorkPolicyInfo();
+ }
+
+ @Test
+ public void handlePreferenceTreeClick_matchingKey_shouldShowWorkPolicyInfo() {
+ when(mEnterpriseProvider.hasWorkPolicyInfo()).thenReturn(true);
+ WorkPolicyInfoPreferenceController controller =
+ new WorkPolicyInfoPreferenceController(mContext, "test_key");
+
+ final Preference pref = new Preference(mContext);
+ pref.setKey(controller.getPreferenceKey());
+ assertThat(controller.handlePreferenceTreeClick(pref)).isTrue();
+ verify(mEnterpriseProvider).showWorkPolicyInfo();
+ }
+}