OSDN Git Service

Add "Your work policy info" entry in Privacy settings
authorIvan Podogov <ginkage@google.com>
Fri, 17 May 2019 14:20:25 +0000 (15:20 +0100)
committerIvan Podogov <ginkage@google.com>
Fri, 14 Jun 2019 12:07:21 +0000 (13:07 +0100)
Bug: 132904820
Test: manual
Change-Id: Id706d450c3ad6a6a8c1e402d39d18e048cdb6519

res/values/config.xml
res/values/strings.xml
res/xml/privacy_dashboard_settings.xml
src/com/android/settings/enterprise/EnterprisePrivacyFeatureProvider.java
src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImpl.java
src/com/android/settings/privacy/WorkPolicyInfoPreferenceController.java [new file with mode: 0644]
tests/robotests/res/values/config.xml
tests/robotests/src/com/android/settings/enterprise/EnterprisePrivacyFeatureProviderImplTest.java
tests/robotests/src/com/android/settings/privacy/WorkPolicyInfoPreferenceControllerTest.java [new file with mode: 0644]

index a85636c..a4a85ba 100755 (executable)
 
     <!-- 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>
index 6e92a3d..0994be4 100644 (file)
 
     <!-- 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>
index aa789b9..3ac6f42 100644 (file)
         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>
index 048782e..46f9b71 100644 (file)
@@ -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();
 }
index 4085988..d095d88 100644 (file)
@@ -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<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;
 
diff --git a/src/com/android/settings/privacy/WorkPolicyInfoPreferenceController.java b/src/com/android/settings/privacy/WorkPolicyInfoPreferenceController.java
new file mode 100644 (file)
index 0000000..45c2c21
--- /dev/null
@@ -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;
+    }
+}
index 15ae899..ca2c61d 100644 (file)
@@ -24,4 +24,7 @@
     <!-- 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>
index 8cbaa76..429ede9 100644 (file)
@@ -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<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))
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 (file)
index 0000000..a92e11e
--- /dev/null
@@ -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();
+    }
+}