From 75a10d1fd26d1b7d128cb22623cb66655586e12d Mon Sep 17 00:00:00 2001 From: Ivan Podogov Date: Fri, 17 May 2019 15:20:25 +0100 Subject: [PATCH] Add "Your work policy info" entry in Privacy settings Bug: 132904820 Test: manual Change-Id: Id706d450c3ad6a6a8c1e402d39d18e048cdb6519 --- res/values/config.xml | 3 + res/values/strings.xml | 6 + res/xml/privacy_dashboard_settings.xml | 10 +- .../EnterprisePrivacyFeatureProvider.java | 13 ++ .../EnterprisePrivacyFeatureProviderImpl.java | 103 +++++++++++++-- .../WorkPolicyInfoPreferenceController.java | 53 ++++++++ tests/robotests/res/values/config.xml | 5 +- ...erprisePrivacyFeatureProviderImplTest.java | 117 ++++++++++++++++++ ...orkPolicyInfoPreferenceControllerTest.java | 97 +++++++++++++++ 9 files changed, 392 insertions(+), 15 deletions(-) create mode 100644 src/com/android/settings/privacy/WorkPolicyInfoPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/privacy/WorkPolicyInfoPreferenceControllerTest.java 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(); + } +}