<!-- List containing the injected tile keys which are suppressed. -->
<string-array name="config_suppress_injected_tile_keys" translatable="false"/>
+
+ <!-- "Show work policy info" intent action. TODO(b/134391103): Replace with final SystemAPI intent when it's available. -->
+ <string name="config_work_policy_info_intent_action" translatable="false"/>
</resources>
<!-- Title for enable MMS notification channel. [CHAR LIMIT=40] -->
<string name="dual_cdma_sim_warning_notification_channel_title">SIM combination</string>
+
+ <!-- Title of setting on privacy settings screen that will show work policy info. [CHAR LIMIT=NONE] -->
+ <string name="work_policy_privacy_settings">Your work policy info</string>
+ <!-- Summary for Enterprise Privacy settings, explaining what the user can expect to find under it [CHAR LIMIT=NONE]-->
+ <string name="work_policy_privacy_settings_summary">Settings managed by your IT admin</string>
+
</resources>
android:title="@string/summary_placeholder"
settings:controller="com.android.settings.privacy.PermissionBarChartPreferenceController"/>
+ <!-- Work Policy info -->
+ <Preference
+ android:key="work_policy_info"
+ android:title="@string/work_policy_privacy_settings"
+ android:summary="@string/work_policy_privacy_settings_summary"
+ settings:allowDividerAbove="true"
+ settings:controller="com.android.settings.privacy.WorkPolicyInfoPreferenceController"/>
+
<!-- Accessibility usage -->
<Preference
android:key="privacy_accessibility_usage"
settings:controller="com.android.settings.privacy.EnableContentCaptureWithServiceSettingsPreferenceController">
</com.android.settings.widget.MasterSwitchPreference>
-</PreferenceScreen>
\ No newline at end of file
+</PreferenceScreen>
* 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();
}
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;
@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
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<ResolveInfo> 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<ResolveInfo> activities = mPm.queryIntentActivitiesAsUser(intent, 0, userId);
+ if (activities.size() != 0) {
+ return intent;
+ }
+
+ return null;
+ }
+
protected static class EnterprisePrivacySpan extends ClickableSpan {
private final Context mContext;
--- /dev/null
+/*
+ * 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;
+ }
+}
<!-- Fake dimen value for restricted icon size - needed to get around Robolectric
issue loading framework hidden resources -->
<dimen name="restricted_icon_size">24dp</dimen>
-</resources>
\ No newline at end of file
+
+ <!-- Fake string to avoid empty intent action -->
+ <string name="config_work_policy_info_intent_action">ACTION_SHOW_WORK_POLICY_INFO</string>
+</resources>
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;
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;
.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<ResolveInfo> 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<Intent> {
+ 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))
--- /dev/null
+/*
+ * 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();
+ }
+}