From f61ccf3fa36ddd1ed462c89d1d9066a4b5dab635 Mon Sep 17 00:00:00 2001 From: Eran Messeri Date: Mon, 2 Apr 2018 23:10:00 +0300 Subject: [PATCH] Require work profile to be unlocked for changing notification settings in a different way Rather than check for the state of the work profile in LockScreenNotificationPreferenceController#handlePreferenceTreeClick, do so in the RestrictedListPreference#performClick. The drawback of checking the state in handlePreferenceTreeClick is that the preferences are displayed first and then the requirement to unlock/enable the work profile is displayed on top of it. This is rather poor UX, so switch to doing the check in performClick and returning early if the work profile needs to be unlocked/enabled. This is similar to Patchset 1 from ag/3805482. The main difference is that the user is returned to the settings screen both after enabling the work profile and unlocking it. Test: Manually with TestDPC Test: atest SettingsRoboTests:RestrictedListPreferenceTest Bug: 77408805 Merged-In: Id168911b082fffac193cd7c7a658ab92d6ce2c15 Change-Id: I0a3a4ec4dda78e28ee88a11d383eda49e9cf50a6 --- .../settings/RestrictedListPreference.java | 37 +++++- ...creenNotificationPreferenceController.java | 39 +----- .../RestrictedListPreferenceTest.java | 124 ++++++++++++++++++ .../testutils/shadow/ShadowUserManager.java | 11 ++ 4 files changed, 173 insertions(+), 38 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/RestrictedListPreferenceTest.java diff --git a/src/com/android/settings/RestrictedListPreference.java b/src/com/android/settings/RestrictedListPreference.java index 25d4fc98d8f..d581af6ad26 100644 --- a/src/com/android/settings/RestrictedListPreference.java +++ b/src/com/android/settings/RestrictedListPreference.java @@ -16,10 +16,16 @@ package com.android.settings; +import android.app.ActivityManager; import android.app.AlertDialog; +import android.app.KeyguardManager; import android.content.Context; import android.content.DialogInterface; +import android.content.Intent; import android.os.Bundle; +import android.os.RemoteException; +import android.os.UserManager; +import android.support.annotation.VisibleForTesting; import android.support.v14.preference.ListPreferenceDialogFragment; import android.support.v7.preference.PreferenceViewHolder; import android.util.AttributeSet; @@ -32,6 +38,7 @@ import android.widget.ImageView; import android.widget.ListAdapter; import android.widget.ListView; +import com.android.settings.Utils; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedPreferenceHelper; @@ -43,6 +50,8 @@ import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; public class RestrictedListPreference extends CustomListPreference { private final RestrictedPreferenceHelper mHelper; private final List mRestrictedItems = new ArrayList<>(); + private boolean mRequiresActiveUnlockedProfile = false; + private int mProfileUserId; public RestrictedListPreference(Context context, AttributeSet attrs) { super(context, attrs); @@ -68,6 +77,24 @@ public class RestrictedListPreference extends CustomListPreference { @Override public void performClick() { + if (mRequiresActiveUnlockedProfile) { + // Check if the profile is started, first. + if (Utils.startQuietModeDialogIfNecessary(getContext(), UserManager.get(getContext()), + mProfileUserId)) { + return; + } + + // Next, check if the profile is unlocked. + KeyguardManager manager = + (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE); + if (manager.isDeviceLocked(mProfileUserId)) { + Intent intent = manager.createConfirmDeviceCredentialIntent( + null, null, mProfileUserId); + getContext().startActivity(intent); + return; + } + } + if (!mHelper.performClick()) { super.performClick(); } @@ -92,6 +119,14 @@ public class RestrictedListPreference extends CustomListPreference { return mHelper.isDisabledByAdmin(); } + public void setRequiresActiveUnlockedProfile(boolean reqState) { + mRequiresActiveUnlockedProfile = reqState; + } + + public void setProfileUserId(int profileUserId) { + mProfileUserId = profileUserId; + } + public boolean isRestrictedForEntry(CharSequence entry) { if (entry == null) { return false; @@ -263,4 +298,4 @@ public class RestrictedListPreference extends CustomListPreference { this.enforcedAdmin = enforcedAdmin; } } -} \ No newline at end of file +} diff --git a/src/com/android/settings/notification/LockScreenNotificationPreferenceController.java b/src/com/android/settings/notification/LockScreenNotificationPreferenceController.java index 2dfe8f35ab6..3b4f00eebb5 100644 --- a/src/com/android/settings/notification/LockScreenNotificationPreferenceController.java +++ b/src/com/android/settings/notification/LockScreenNotificationPreferenceController.java @@ -19,16 +19,12 @@ package com.android.settings.notification; import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_NOTIFICATIONS; import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS; -import android.app.ActivityManager; import android.app.admin.DevicePolicyManager; -import android.app.KeyguardManager; import android.content.ContentResolver; import android.content.Context; -import android.content.Intent; import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; -import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; @@ -101,6 +97,8 @@ public class LockScreenNotificationPreferenceController extends AbstractPreferen } if (mProfileUserId != UserHandle.USER_NULL) { mLockscreenProfile = (RestrictedListPreference) screen.findPreference(mWorkSettingKey); + mLockscreenProfile.setRequiresActiveUnlockedProfile(true); + mLockscreenProfile.setProfileUserId(mProfileUserId); } else { setVisible(screen, mWorkSettingKey, false /* visible */); setVisible(screen, mWorkSettingCategoryKey, false /* visible */); @@ -244,39 +242,6 @@ public class LockScreenNotificationPreferenceController extends AbstractPreferen return false; } - @Override - public boolean handlePreferenceTreeClick(Preference preference) { - final String key = preference.getKey(); - if (!TextUtils.equals(mWorkSettingKey, key)) { - return false; - } - - // Check if the profile is started, first. - if (Utils.startQuietModeDialogIfNecessary(mContext, UserManager.get(mContext), - mProfileUserId)) { - return true; - } - - // Next, check if the profile is unlocked. - KeyguardManager manager = - (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); - if (manager.isDeviceLocked(mProfileUserId)) { - //TODO: Figure out how to return the user to the current activity so they - //don't have to navigate to the settings again. - Intent intent = manager.createConfirmDeviceCredentialIntent( - null, null, mProfileUserId); - try { - ActivityManager.getService().startConfirmDeviceCredentialIntent(intent, - null /*options*/); - } catch (RemoteException ignored) { - } - - return true; - } - - return false; - } - private void setRestrictedIfNotificationFeaturesDisabled(CharSequence entry, CharSequence entryValue, int keyguardNotificationFeatures) { RestrictedLockUtils.EnforcedAdmin admin = diff --git a/tests/robotests/src/com/android/settings/RestrictedListPreferenceTest.java b/tests/robotests/src/com/android/settings/RestrictedListPreferenceTest.java new file mode 100644 index 00000000000..1cca8428761 --- /dev/null +++ b/tests/robotests/src/com/android/settings/RestrictedListPreferenceTest.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2018 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; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.robolectric.RuntimeEnvironment.application; +import static org.robolectric.Shadows.shadowOf; + +import android.app.KeyguardManager; +import android.content.Intent; +import android.os.Bundle; +import android.util.AttributeSet; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowUserManager; +import com.android.settingslib.RestrictedPreferenceHelper; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.Shadows; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowKeyguardManager; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config( + shadows = { + ShadowUserManager.class, + ShadowKeyguardManager.class, + }) +public class RestrictedListPreferenceTest { + private static final int PROFILE_USER_ID = 11; + // From UnlaunchableAppActivity + private static final int UNLAUNCHABLE_REASON_QUIET_MODE = 1; + private static final String EXTRA_UNLAUNCHABLE_REASON = "unlaunchable_reason"; + + private ShadowUserManager mShadowUserManager; + private ShadowKeyguardManager mShadowKeyguardManager; + private RestrictedListPreference mPreference; + private RestrictedPreferenceHelper mMockHelper; + + @Before + public void setUp() { + mShadowKeyguardManager = + Shadows.shadowOf(application.getSystemService(KeyguardManager.class)); + mMockHelper = mock(RestrictedPreferenceHelper.class); + mShadowUserManager = ShadowUserManager.getShadow(); + mPreference = new RestrictedListPreference(application, mock(AttributeSet.class)); + mPreference.setProfileUserId(PROFILE_USER_ID); + ReflectionHelpers.setField(mPreference, "mHelper", mMockHelper); + } + + @Test + public void performClick_profileLocked() { + mPreference.setRequiresActiveUnlockedProfile(true); + mShadowUserManager.setQuietModeEnabled(false); + mShadowKeyguardManager.setIsDeviceLocked(PROFILE_USER_ID, true); + // Device has to be marked as secure so the real KeyguardManager will create a non-null + // intent. + mShadowKeyguardManager.setIsDeviceSecure(PROFILE_USER_ID, true); + mPreference.performClick(); + // Make sure that the performClick method on the helper is never reached. + verify(mMockHelper, never()).performClick(); + // Assert that a CONFIRM_DEVICE_CREDENTIAL intent has been started. + Intent started = shadowOf(application).getNextStartedActivity(); + assertThat(started.getExtras().getInt(Intent.EXTRA_USER_ID)).isEqualTo(PROFILE_USER_ID); + assertThat(started.getAction()) + .isEqualTo(KeyguardManager.ACTION_CONFIRM_DEVICE_CREDENTIAL_WITH_USER); + } + + @Test + public void performClick_profileDisabled() { + mPreference.setRequiresActiveUnlockedProfile(true); + mShadowUserManager.setQuietModeEnabled(true); + mShadowKeyguardManager.setIsDeviceLocked(PROFILE_USER_ID, false); + mPreference.performClick(); + // Make sure that the performClick method on the helper is never reached. + verify(mMockHelper, never()).performClick(); + // Assert that a new intent for enabling the work profile is started. + Intent started = shadowOf(application).getNextStartedActivity(); + Bundle extras = started.getExtras(); + int reason = extras.getInt(EXTRA_UNLAUNCHABLE_REASON); + assertThat(reason).isEqualTo(UNLAUNCHABLE_REASON_QUIET_MODE); + } + + @Test + public void performClick_profileAvailable() { + // Verify that the helper's perfomClick method is called if the profile is + // available and unlocked. + mPreference.setRequiresActiveUnlockedProfile(true); + mShadowUserManager.setQuietModeEnabled(false); + mShadowKeyguardManager.setIsDeviceLocked(PROFILE_USER_ID, false); + when(mMockHelper.performClick()).thenReturn(true); + mPreference.performClick(); + verify(mMockHelper).performClick(); + } + + @Test + public void performClick_profileLockedAndUnlockedProfileNotRequired() { + // Verify that even if the profile is disabled, if the Preference class does not + // require it than the regular flow takes place. + mPreference.setRequiresActiveUnlockedProfile(false); + mShadowUserManager.setQuietModeEnabled(true); + when(mMockHelper.performClick()).thenReturn(true); + mPreference.performClick(); + verify(mMockHelper).performClick(); + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java index f7fd12f58fe..d83c8148fff 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java @@ -44,6 +44,7 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager private final Map> mRestrictionSources = new HashMap<>(); private final List mUserProfileInfos = new ArrayList<>(); private final Set mManagedProfiles = new HashSet<>(); + private boolean mIsQuietModeEnabled = false; @Resetter public void reset() { @@ -52,6 +53,7 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager mUserProfileInfos.clear(); mRestrictionSources.clear(); mManagedProfiles.clear(); + mIsQuietModeEnabled = false; } public void setUserInfo(int userHandle, UserInfo userInfo) { @@ -110,4 +112,13 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager public void addManagedProfile(int userId) { mManagedProfiles.add(userId); } + + @Implementation + public boolean isQuietModeEnabled(UserHandle userHandle) { + return mIsQuietModeEnabled; + } + + public void setQuietModeEnabled(boolean enabled) { + mIsQuietModeEnabled = enabled; + } }