diff --git a/res/values/strings.xml b/res/values/strings.xml index bd7a47cee67..28973d76281 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -7365,9 +7365,9 @@ Don\'t update - Your phone mutes sounds & vibrations\n\nUpdate settings to also:\n\n- Hide notifications\n\n- Only show calls from starred contacts + Your phone can do more to help you focus.\n\nUpdate settings to:\n\n- Hide notifications completely\n\n- Allow calls from starred contacts and repeat callers - Update your Do Not Disturb settings? + Update Do Not Disturb? Settings 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/bluetooth/BluetoothDevicePreference.java b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java index 3fd7ced3e03..2cc623331e2 100644 --- a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java +++ b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java @@ -56,6 +56,7 @@ public final class BluetoothDevicePreference extends GearPreference implements private AlertDialog mDisconnectDialog; private String contentDescription = null; + private boolean mHideSecondTarget = false; /* Talk-back descriptions for various BT icons */ Resources mResources; @@ -86,7 +87,8 @@ public final class BluetoothDevicePreference extends GearPreference implements protected boolean shouldHideSecondTarget() { return mCachedDevice == null || mCachedDevice.getBondState() != BluetoothDevice.BOND_BONDED - || mUserManager.hasUserRestriction(DISALLOW_CONFIG_BLUETOOTH); + || mUserManager.hasUserRestriction(DISALLOW_CONFIG_BLUETOOTH) + || mHideSecondTarget; } @Override @@ -112,6 +114,10 @@ public final class BluetoothDevicePreference extends GearPreference implements return mCachedDevice; } + public void hideSecondTarget(boolean hideSecondTarget) { + mHideSecondTarget = hideSecondTarget; + } + public void onDeviceAttributesChanged() { /* * The preference framework takes care of making sure the value has diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java index 595d9518944..dfcbfca1e06 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java @@ -56,29 +56,14 @@ public abstract class BluetoothDeviceUpdater implements BluetoothCallback, protected final DevicePreferenceCallback mDevicePreferenceCallback; protected final Map mPreferenceMap; protected Context mPrefContext; + protected DashboardFragment mFragment; private final boolean mShowDeviceWithoutNames; - private DashboardFragment mFragment; private Preference.OnPreferenceClickListener mDevicePreferenceClickListener = null; @VisibleForTesting final GearPreference.OnGearClickListener mDeviceProfilesListener = pref -> { - final CachedBluetoothDevice device = - ((BluetoothDevicePreference) pref).getBluetoothDevice(); - if (device == null) { - return; - } - final Bundle args = new Bundle(); - args.putString(BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS, - device.getDevice().getAddress()); - - new SubSettingLauncher(mFragment.getContext()) - .setDestination(BluetoothDeviceDetailsFragment.class.getName()) - .setArguments(args) - .setTitle(R.string.device_details_title) - .setSourceMetricsCategory(mFragment.getMetricsCategory()) - .launch(); - + launchDeviceDetails(pref); }; private class PreferenceClickListener implements @@ -201,7 +186,7 @@ public abstract class BluetoothDeviceUpdater implements BluetoothCallback, public abstract boolean isFilterMatched(CachedBluetoothDevice cachedBluetoothDevice); /** - * Update whether to show {@cde cachedBluetoothDevice} in the list. + * Update whether to show {@link CachedBluetoothDevice} in the list. */ protected void update(CachedBluetoothDevice cachedBluetoothDevice) { if (isFilterMatched(cachedBluetoothDevice)) { @@ -239,6 +224,28 @@ public abstract class BluetoothDeviceUpdater implements BluetoothCallback, } } + /** + * Get {@link CachedBluetoothDevice} from {@link Preference} and it is used to init + * {@link SubSettingLauncher} to launch {@link BluetoothDeviceDetailsFragment} + */ + protected void launchDeviceDetails(Preference preference) { + final CachedBluetoothDevice device = + ((BluetoothDevicePreference) preference).getBluetoothDevice(); + if (device == null) { + return; + } + final Bundle args = new Bundle(); + args.putString(BluetoothDeviceDetailsFragment.KEY_DEVICE_ADDRESS, + device.getDevice().getAddress()); + + new SubSettingLauncher(mFragment.getContext()) + .setDestination(BluetoothDeviceDetailsFragment.class.getName()) + .setArguments(args) + .setTitle(R.string.device_details_title) + .setSourceMetricsCategory(mFragment.getMetricsCategory()) + .launch(); + } + /** * @return {@code true} if {@code cachedBluetoothDevice} is connected * and the bond state is bonded. diff --git a/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java index 55f4bb1dd6b..11702bcefc2 100644 --- a/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java +++ b/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdater.java @@ -16,11 +16,14 @@ package com.android.settings.bluetooth; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.media.AudioManager; import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.Preference; import android.util.Log; + import com.android.settings.connecteddevice.DevicePreferenceCallback; import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.bluetooth.CachedBluetoothDevice; @@ -116,4 +119,20 @@ public class ConnectedBluetoothDeviceUpdater extends BluetoothDeviceUpdater { } return isFilterMatched; } + + @Override + protected void addPreference(CachedBluetoothDevice cachedDevice) { + super.addPreference(cachedDevice); + final BluetoothDevice device = cachedDevice.getDevice(); + if (mPreferenceMap.containsKey(device)) { + final BluetoothDevicePreference btPreference = + (BluetoothDevicePreference) mPreferenceMap.get(device); + btPreference.setOnGearClickListener(null); + btPreference.hideSecondTarget(true); + btPreference.setOnPreferenceClickListener((Preference p) -> { + launchDeviceDetails(p); + return true; + }); + } + } } diff --git a/src/com/android/settings/network/PrivateDnsPreferenceController.java b/src/com/android/settings/network/PrivateDnsPreferenceController.java index 50224caba1a..47aa4dcffe4 100644 --- a/src/com/android/settings/network/PrivateDnsPreferenceController.java +++ b/src/com/android/settings/network/PrivateDnsPreferenceController.java @@ -24,6 +24,10 @@ import android.content.Context; import android.content.ContentResolver; import android.content.res.Resources; import android.database.ContentObserver; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +import android.net.LinkProperties; +import android.net.Network; import android.net.Uri; import android.os.Handler; import android.os.Looper; @@ -31,6 +35,7 @@ import android.provider.Settings; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceScreen; +import com.android.internal.util.ArrayUtils; import com.android.settings.R; import com.android.settings.core.BasePreferenceController; import com.android.settings.core.PreferenceControllerMixin; @@ -38,6 +43,8 @@ import com.android.settingslib.core.lifecycle.events.OnStart; import com.android.settingslib.core.lifecycle.events.OnStop; import com.android.settingslib.core.lifecycle.LifecycleObserver; +import java.net.InetAddress; +import java.util.List; public class PrivateDnsPreferenceController extends BasePreferenceController implements PreferenceControllerMixin, LifecycleObserver, OnStart, OnStop { @@ -50,12 +57,15 @@ public class PrivateDnsPreferenceController extends BasePreferenceController private final Handler mHandler; private final ContentObserver mSettingsObserver; + private final ConnectivityManager mConnectivityManager; + private LinkProperties mLatestLinkProperties; private Preference mPreference; public PrivateDnsPreferenceController(Context context) { super(context, KEY_PRIVATE_DNS_SETTINGS); mHandler = new Handler(Looper.getMainLooper()); mSettingsObserver = new PrivateDnsSettingsObserver(mHandler); + mConnectivityManager = context.getSystemService(ConnectivityManager.class); } @Override @@ -80,11 +90,17 @@ public class PrivateDnsPreferenceController extends BasePreferenceController for (Uri uri : SETTINGS_URIS) { mContext.getContentResolver().registerContentObserver(uri, false, mSettingsObserver); } + final Network defaultNetwork = mConnectivityManager.getActiveNetwork(); + if (defaultNetwork != null) { + mLatestLinkProperties = mConnectivityManager.getLinkProperties(defaultNetwork); + } + mConnectivityManager.registerDefaultNetworkCallback(mNetworkCallback, mHandler); } @Override public void onStop() { mContext.getContentResolver().unregisterContentObserver(mSettingsObserver); + mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); } @Override @@ -92,13 +108,23 @@ public class PrivateDnsPreferenceController extends BasePreferenceController final Resources res = mContext.getResources(); final ContentResolver cr = mContext.getContentResolver(); final String mode = PrivateDnsModeDialogPreference.getModeFromSettings(cr); + final LinkProperties lp = mLatestLinkProperties; + final List dnses = (lp == null) ? null : lp.getValidatedPrivateDnsServers(); + final boolean dnsesResolved = !ArrayUtils.isEmpty(dnses); switch (mode) { case PRIVATE_DNS_MODE_OFF: return res.getString(R.string.private_dns_mode_off); case PRIVATE_DNS_MODE_OPPORTUNISTIC: - return res.getString(R.string.private_dns_mode_opportunistic); + // TODO (b/79122154) : create a string specifically for this, instead of + // hijacking a string from notifications. This is necessary at this time + // because string freeze is in the past and this string has the right + // content at this moment. + return dnsesResolved ? res.getString(R.string.switch_on_text) + : res.getString(R.string.private_dns_mode_opportunistic); case PRIVATE_DNS_MODE_PROVIDER_HOSTNAME: - return PrivateDnsModeDialogPreference.getHostnameFromSettings(cr); + return dnsesResolved + ? PrivateDnsModeDialogPreference.getHostnameFromSettings(cr) + : res.getString(R.string.private_dns_mode_provider_failure); } return ""; } @@ -111,8 +137,25 @@ public class PrivateDnsPreferenceController extends BasePreferenceController @Override public void onChange(boolean selfChange) { if (mPreference != null) { - PrivateDnsPreferenceController.this.updateState(mPreference); + updateState(mPreference); } } } + + private final NetworkCallback mNetworkCallback = new NetworkCallback() { + @Override + public void onLinkPropertiesChanged(Network network, LinkProperties lp) { + mLatestLinkProperties = lp; + if (mPreference != null) { + updateState(mPreference); + } + } + @Override + public void onLost(Network network) { + mLatestLinkProperties = null; + if (mPreference != null) { + updateState(mPreference); + } + } + }; } 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/src/com/android/settings/notification/ZenOnboardingActivity.java b/src/com/android/settings/notification/ZenOnboardingActivity.java index baca8110ff3..9d71f54c712 100644 --- a/src/com/android/settings/notification/ZenOnboardingActivity.java +++ b/src/com/android/settings/notification/ZenOnboardingActivity.java @@ -18,12 +18,10 @@ package com.android.settings.notification; import android.app.Activity; import android.app.NotificationManager; -import android.content.Intent; +import android.app.NotificationManager.Policy; import android.os.Bundle; -import android.provider.Settings; import android.support.annotation.VisibleForTesting; import android.view.View; -import android.widget.CheckBox; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; @@ -67,12 +65,13 @@ public class ZenOnboardingActivity extends Activity { public void save(View button) { mMetrics.action(MetricsEvent.ACTION_ZEN_ONBOARDING_OK); - NotificationManager.Policy policy = mNm.getNotificationPolicy(); + Policy policy = mNm.getNotificationPolicy(); - NotificationManager.Policy newPolicy = new NotificationManager.Policy( - policy.priorityCategories, NotificationManager.Policy.PRIORITY_SENDERS_STARRED, + Policy newPolicy = new NotificationManager.Policy( + Policy.PRIORITY_CATEGORY_REPEAT_CALLERS | policy.priorityCategories, + Policy.PRIORITY_SENDERS_STARRED, policy.priorityMessageSenders, - NotificationManager.Policy.getAllSuppressedVisualEffects()); + Policy.getAllSuppressedVisualEffects()); mNm.setNotificationPolicy(newPolicy); finishAndRemoveTask(); diff --git a/src/com/android/settings/widget/SwitchBar.java b/src/com/android/settings/widget/SwitchBar.java index c33f6032cac..004e89c0ed6 100644 --- a/src/com/android/settings/widget/SwitchBar.java +++ b/src/com/android/settings/widget/SwitchBar.java @@ -25,6 +25,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.ColorInt; import android.support.annotation.StringRes; +import android.support.annotation.VisibleForTesting; import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.text.style.TextAppearanceSpan; @@ -132,6 +133,17 @@ public class SwitchBar extends LinearLayout implements CompoundButton.OnCheckedC (switchView, isChecked) -> setTextViewLabelAndBackground(isChecked)); mRestrictedIcon = findViewById(R.id.restricted_icon); + mRestrictedIcon.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mDisabledByAdmin) { + mMetricsFeatureProvider.count(mContext, + mMetricsTag + "/switch_bar|restricted", 1); + RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context, + mEnforcedAdmin); + } + } + }); // Default is hide setVisibility(View.GONE); @@ -196,6 +208,11 @@ public class SwitchBar extends LinearLayout implements CompoundButton.OnCheckedC mSwitch.setEnabled(enabled); } + @VisibleForTesting + View getDelegatingView() { + return mDisabledByAdmin ? mRestrictedIcon : mSwitch; + } + /** * If admin is not null, disables the text and switch but keeps the view clickable. * Otherwise, calls setEnabled which will enables the entire view including @@ -216,6 +233,8 @@ public class SwitchBar extends LinearLayout implements CompoundButton.OnCheckedC mRestrictedIcon.setVisibility(View.GONE); setEnabled(true); } + setTouchDelegate(new TouchDelegate(new Rect(0, 0, getWidth(), getHeight()), + getDelegatingView())); } public final ToggleSwitch getSwitch() { @@ -228,7 +247,8 @@ public class SwitchBar extends LinearLayout implements CompoundButton.OnCheckedC mSwitch.setOnCheckedChangeListener(this); // Make the entire bar work as a switch post(() -> setTouchDelegate( - new TouchDelegate(new Rect(0, 0, getWidth(), getHeight()), mSwitch))); + new TouchDelegate(new Rect(0, 0, getWidth(), getHeight()), + getDelegatingView()))); } } @@ -242,7 +262,8 @@ public class SwitchBar extends LinearLayout implements CompoundButton.OnCheckedC @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { if ((w > 0) && (h > 0)) { - setTouchDelegate(new TouchDelegate(new Rect(0, 0, w, h), mSwitch)); + setTouchDelegate(new TouchDelegate(new Rect(0, 0, w, h), + getDelegatingView())); } } 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/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java index 9d69f59b7d3..56e638a9ed1 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java @@ -15,6 +15,7 @@ */ package com.android.settings.bluetooth; +import static com.google.common.truth.Truth.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; @@ -207,4 +208,15 @@ public class ConnectedBluetoothDeviceUpdaterTest { verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice); } + + @Test + public void addPreference_addPreference_shouldHideSecondTarget() { + BluetoothDevicePreference btPreference = + new BluetoothDevicePreference(mContext, mCachedBluetoothDevice, true); + mBluetoothDeviceUpdater.mPreferenceMap.put(mBluetoothDevice, btPreference); + + mBluetoothDeviceUpdater.addPreference(mCachedBluetoothDevice); + + assertThat(btPreference.shouldHideSecondTarget()).isTrue(); + } } diff --git a/tests/robotests/src/com/android/settings/network/PrivateDnsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/PrivateDnsPreferenceControllerTest.java index 83d4bd530b7..ce40ab69364 100644 --- a/tests/robotests/src/com/android/settings/network/PrivateDnsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/network/PrivateDnsPreferenceControllerTest.java @@ -24,17 +24,29 @@ import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME import static android.provider.Settings.Global.PRIVATE_DNS_MODE; import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.CALLS_REAL_METHODS; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.withSettings; import static org.mockito.Mockito.when; import android.arch.lifecycle.LifecycleOwner; import android.content.Context; import android.content.ContentResolver; import android.database.ContentObserver; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +import android.net.LinkProperties; +import android.net.Network; +import android.os.Handler; import android.provider.Settings; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceScreen; @@ -46,22 +58,45 @@ import com.android.settingslib.core.lifecycle.Lifecycle; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowContentResolver; +import org.robolectric.shadows.ShadowServiceManager; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; @RunWith(SettingsRobolectricTestRunner.class) public class PrivateDnsPreferenceControllerTest { private final static String HOSTNAME = "dns.example.com"; + private final static List NON_EMPTY_ADDRESS_LIST; + static { + try { + NON_EMPTY_ADDRESS_LIST = Arrays.asList( + InetAddress.getByAddress(new byte[] { 8, 8, 8, 8 })); + } catch (UnknownHostException e) { + throw new RuntimeException("Invalid hardcoded IP addresss: " + e); + } + } @Mock private PreferenceScreen mScreen; @Mock + private ConnectivityManager mConnectivityManager; + @Mock + private Network mNetwork; + @Mock private Preference mPreference; + @Captor + private ArgumentCaptor mCallbackCaptor; private PrivateDnsPreferenceController mController; private Context mContext; private ContentResolver mContentResolver; @@ -75,15 +110,41 @@ public class PrivateDnsPreferenceControllerTest { mContext = spy(RuntimeEnvironment.application); mContentResolver = mContext.getContentResolver(); mShadowContentResolver = Shadow.extract(mContentResolver); + when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)) + .thenReturn(mConnectivityManager); + doNothing().when(mConnectivityManager).registerDefaultNetworkCallback( + mCallbackCaptor.capture(), nullable(Handler.class)); when(mScreen.findPreference(anyString())).thenReturn(mPreference); mController = spy(new PrivateDnsPreferenceController(mContext)); + mLifecycleOwner = () -> mLifecycle; mLifecycle = new Lifecycle(mLifecycleOwner); mLifecycle.addObserver(mController); } + private void updateLinkProperties(LinkProperties lp) { + NetworkCallback nc = mCallbackCaptor.getValue(); + // The network callback that has been captured by the captor is the `mNetworkCallback' + // member of mController. mController being a spy, it has copied that member from the + // original object it was spying on, which means the object returned by the captor + // has a reference to the original object instead of the mock as its outer instance + // and will call methods and modify members of the original object instead of the spy, + // so methods subsequently called on the spy will not be aware of the changes. To work + // around this, the following code will create a new instance of the same class with + // the same code, but it sets the spy as the outer instance. + // A more recent version of Mockito would have made possible to create the spy with + // spy(PrivateDnsPreferenceController.class, withSettings().useConstructor(mContext)) + // and that would have solved the problem by removing the original object entirely + // in a more elegant manner, but useConstructor(Object...) is only available starting + // with Mockito 2.7.14. Other solutions involve modifying the code under test for + // the sake of the test. + nc = mock(nc.getClass(), withSettings().useConstructor().outerInstance(mController) + .defaultAnswer(CALLS_REAL_METHODS)); + nc.onLinkPropertiesChanged(mNetwork, lp); + } + @Test public void goThroughLifecycle_shouldRegisterUnregisterSettingsObserver() { mLifecycle.handleLifecycleEvent(ON_START); @@ -113,20 +174,50 @@ public class PrivateDnsPreferenceControllerTest { @Test public void getSummary_PrivateDnsModeOpportunistic() { + mLifecycle.handleLifecycleEvent(ON_START); setPrivateDnsMode(PRIVATE_DNS_MODE_OPPORTUNISTIC); setPrivateDnsProviderHostname(HOSTNAME); mController.updateState(mPreference); verify(mController, atLeastOnce()).getSummary(); verify(mPreference).setSummary(getResourceString(R.string.private_dns_mode_opportunistic)); + + LinkProperties lp = mock(LinkProperties.class); + when(lp.getValidatedPrivateDnsServers()).thenReturn(NON_EMPTY_ADDRESS_LIST); + updateLinkProperties(lp); + mController.updateState(mPreference); + verify(mPreference).setSummary(getResourceString(R.string.switch_on_text)); + + reset(mPreference); + lp = mock(LinkProperties.class); + when(lp.getValidatedPrivateDnsServers()).thenReturn(Collections.emptyList()); + updateLinkProperties(lp); + mController.updateState(mPreference); + verify(mPreference).setSummary(getResourceString(R.string.private_dns_mode_opportunistic)); } @Test public void getSummary_PrivateDnsModeProviderHostname() { + mLifecycle.handleLifecycleEvent(ON_START); setPrivateDnsMode(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME); setPrivateDnsProviderHostname(HOSTNAME); mController.updateState(mPreference); verify(mController, atLeastOnce()).getSummary(); + verify(mPreference).setSummary( + getResourceString(R.string.private_dns_mode_provider_failure)); + + LinkProperties lp = mock(LinkProperties.class); + when(lp.getValidatedPrivateDnsServers()).thenReturn(NON_EMPTY_ADDRESS_LIST); + updateLinkProperties(lp); + mController.updateState(mPreference); verify(mPreference).setSummary(HOSTNAME); + + reset(mPreference); + lp = mock(LinkProperties.class); + when(lp.getValidatedPrivateDnsServers()).thenReturn(Collections.emptyList()); + updateLinkProperties(lp); + mController.updateState(mPreference); + verify(mPreference).setSummary( + getResourceString(R.string.private_dns_mode_provider_failure)); } private void setPrivateDnsMode(String mode) { diff --git a/tests/robotests/src/com/android/settings/notification/ZenOnboardingActivityTest.java b/tests/robotests/src/com/android/settings/notification/ZenOnboardingActivityTest.java index 57dc855a780..7bc93713977 100644 --- a/tests/robotests/src/com/android/settings/notification/ZenOnboardingActivityTest.java +++ b/tests/robotests/src/com/android/settings/notification/ZenOnboardingActivityTest.java @@ -17,6 +17,7 @@ package com.android.settings.notification; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS; +import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS; import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE; @@ -94,7 +95,8 @@ public class ZenOnboardingActivityTest { verify(mNm).setNotificationPolicy(captor.capture()); Policy actual = captor.getValue(); - assertThat(actual.priorityCategories).isEqualTo(PRIORITY_CATEGORY_ALARMS); + assertThat(actual.priorityCategories).isEqualTo(PRIORITY_CATEGORY_ALARMS + | PRIORITY_CATEGORY_REPEAT_CALLERS); assertThat(actual.priorityCallSenders).isEqualTo(Policy.PRIORITY_SENDERS_STARRED); assertThat(actual.priorityMessageSenders).isEqualTo(Policy.PRIORITY_SENDERS_ANY); assertThat(actual.suppressedVisualEffects).isEqualTo( 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; + } } diff --git a/tests/robotests/src/com/android/settings/widget/SwitchBarTest.java b/tests/robotests/src/com/android/settings/widget/SwitchBarTest.java index bd99bbbbb8f..818daecd26d 100644 --- a/tests/robotests/src/com/android/settings/widget/SwitchBarTest.java +++ b/tests/robotests/src/com/android/settings/widget/SwitchBarTest.java @@ -26,6 +26,7 @@ import android.widget.TextView; import com.android.settings.R; import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -82,4 +83,16 @@ public class SwitchBarTest { assertThat(((TextView) mBar.findViewById(R.id.switch_text)).getText()) .isEqualTo(mContext.getString(onText)); } + + @Test + public void disabledByAdmin_shouldDelegateToRestrictedIcon() { + mBar.setDisabledByAdmin(new EnforcedAdmin()); + assertThat(mBar.getDelegatingView().getId()).isEqualTo(R.id.restricted_icon); + } + + @Test + public void notDisabledByAdmin_shouldDelegateToSwitch() { + mBar.setDisabledByAdmin(null); + assertThat(mBar.getDelegatingView().getId()).isEqualTo(R.id.switch_widget); + } }