diff --git a/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceController.java b/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceController.java index 0593aa0256b..a3cff3da3d4 100644 --- a/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceController.java +++ b/src/com/android/settings/display/AmbientDisplayAlwaysOnPreferenceController.java @@ -43,10 +43,7 @@ public class AmbientDisplayAlwaysOnPreferenceController extends TogglePreference @Override public int getAvailabilityStatus() { - if (mConfig == null) { - mConfig = new AmbientDisplayConfiguration(mContext); - } - return isAvailable(mConfig) ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + return isAvailable(getConfig()) ? AVAILABLE : UNSUPPORTED_ON_DEVICE; } @Override @@ -56,7 +53,7 @@ public class AmbientDisplayAlwaysOnPreferenceController extends TogglePreference @Override public boolean isChecked() { - return mConfig.alwaysOnEnabled(MY_USER); + return getConfig().alwaysOnEnabled(MY_USER); } @Override @@ -82,15 +79,14 @@ public class AmbientDisplayAlwaysOnPreferenceController extends TogglePreference return this; } - public static boolean isAlwaysOnEnabled(AmbientDisplayConfiguration config) { - return config.alwaysOnEnabled(MY_USER); - } - public static boolean isAvailable(AmbientDisplayConfiguration config) { return config.alwaysOnAvailableForUser(MY_USER); } - public static boolean accessibilityInversionEnabled(AmbientDisplayConfiguration config) { - return config.accessibilityInversionEnabled(MY_USER); + private AmbientDisplayConfiguration getConfig() { + if (mConfig == null) { + mConfig = new AmbientDisplayConfiguration(mContext); + } + return mConfig; } } diff --git a/src/com/android/settings/gestures/DoubleTapScreenPreferenceController.java b/src/com/android/settings/gestures/DoubleTapScreenPreferenceController.java index 47952291028..04bdedcccef 100644 --- a/src/com/android/settings/gestures/DoubleTapScreenPreferenceController.java +++ b/src/com/android/settings/gestures/DoubleTapScreenPreferenceController.java @@ -35,7 +35,6 @@ public class DoubleTapScreenPreferenceController extends GesturePreferenceContro private final int OFF = 0; private static final String PREF_KEY_VIDEO = "gesture_double_tap_screen_video"; - private final String mDoubleTapScreenPrefKey; private final String SECURE_KEY = DOZE_PULSE_ON_DOUBLE_TAP; @@ -46,7 +45,6 @@ public class DoubleTapScreenPreferenceController extends GesturePreferenceContro public DoubleTapScreenPreferenceController(Context context, String key) { super(context, key); mUserId = UserHandle.myUserId(); - mDoubleTapScreenPrefKey = key; } public DoubleTapScreenPreferenceController setConfig(AmbientDisplayConfiguration config) { @@ -67,17 +65,13 @@ public class DoubleTapScreenPreferenceController extends GesturePreferenceContro @Override public int getAvailabilityStatus() { - if (mAmbientConfig == null) { - mAmbientConfig = new AmbientDisplayConfiguration(mContext); - } - // No hardware support for Double Tap - if (!mAmbientConfig.doubleTapSensorAvailable()) { + if (!getAmbientConfig().doubleTapSensorAvailable()) { return UNSUPPORTED_ON_DEVICE; } // Can't change Double Tap when AOD is enabled. - if (!mAmbientConfig.ambientDisplayAvailable()) { + if (!getAmbientConfig().ambientDisplayAvailable()) { return DISABLED_DEPENDENT_SETTING; } @@ -102,11 +96,18 @@ public class DoubleTapScreenPreferenceController extends GesturePreferenceContro @Override public boolean isChecked() { - return mAmbientConfig.pulseOnDoubleTapEnabled(mUserId); + return getAmbientConfig().pulseOnDoubleTapEnabled(mUserId); } @Override protected boolean canHandleClicks() { - return !mAmbientConfig.alwaysOnEnabled(mUserId); + return !getAmbientConfig().alwaysOnEnabled(mUserId); } -} \ No newline at end of file + + private AmbientDisplayConfiguration getAmbientConfig() { + if (mAmbientConfig == null) { + mAmbientConfig = new AmbientDisplayConfiguration(mContext); + } + return mAmbientConfig; + } +} diff --git a/src/com/android/settings/notification/ZenRulePreference.java b/src/com/android/settings/notification/ZenRulePreference.java index 60e4bdeebac..1be585760d4 100644 --- a/src/com/android/settings/notification/ZenRulePreference.java +++ b/src/com/android/settings/notification/ZenRulePreference.java @@ -113,7 +113,7 @@ public class ZenRulePreference extends TwoTargetPreference { protected void setAttributes(AutomaticZenRule rule) { final boolean isSchedule = ZenModeConfig.isValidScheduleConditionId( - rule.getConditionId()); + rule.getConditionId(), true); final boolean isEvent = ZenModeConfig.isValidEventConditionId(rule.getConditionId()); final boolean isSystemRule = isSchedule || isEvent; diff --git a/src/com/android/settings/security/LockUnificationPreferenceController.java b/src/com/android/settings/security/LockUnificationPreferenceController.java index 57fe6164227..a8fa744f0a2 100644 --- a/src/com/android/settings/security/LockUnificationPreferenceController.java +++ b/src/com/android/settings/security/LockUnificationPreferenceController.java @@ -43,6 +43,17 @@ import com.android.settingslib.core.AbstractPreferenceController; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; +/** + * Controller for password unification/un-unification flows. + * + * When password is being unified, there may be two cases: + * 1. If work password is not empty and satisfies device-wide policies (if any), it will be made + * into device-wide password. To do that we need both current device and profile passwords + * because both of them will be changed as a result. + * 2. Otherwise device-wide password is preserved. In this case we only need current profile + * password, but after unifying the passwords we proceed to ask the user for a new device + * password. + */ public class LockUnificationPreferenceController extends AbstractPreferenceController implements PreferenceControllerMixin, Preference.OnPreferenceChangeListener { @@ -51,8 +62,9 @@ public class LockUnificationPreferenceController extends AbstractPreferenceContr private static final int MY_USER_ID = UserHandle.myUserId(); private final UserManager mUm; + private final DevicePolicyManager mDpm; private final LockPatternUtils mLockPatternUtils; - private final int mProfileChallengeUserId; + private final int mProfileUserId; private final SecuritySettings mHost; private RestrictedSwitchPreference mUnifyProfile; @@ -60,6 +72,7 @@ public class LockUnificationPreferenceController extends AbstractPreferenceContr private String mCurrentDevicePassword; private String mCurrentProfilePassword; + private boolean mKeepDeviceLock; @Override public void displayPreference(PreferenceScreen screen) { @@ -70,20 +83,18 @@ public class LockUnificationPreferenceController extends AbstractPreferenceContr public LockUnificationPreferenceController(Context context, SecuritySettings host) { super(context); mHost = host; - mUm = (UserManager) context.getSystemService(Context.USER_SERVICE); + mUm = context.getSystemService(UserManager.class); + mDpm = context.getSystemService(DevicePolicyManager.class); mLockPatternUtils = FeatureFactory.getFactory(context) .getSecurityFeatureProvider() .getLockPatternUtils(context); - mProfileChallengeUserId = Utils.getManagedProfileId(mUm, MY_USER_ID); + mProfileUserId = Utils.getManagedProfileId(mUm, MY_USER_ID); } @Override public boolean isAvailable() { - final boolean allowSeparateProfileChallenge = - mProfileChallengeUserId != UserHandle.USER_NULL - && mLockPatternUtils.isSeparateProfileChallengeAllowed( - mProfileChallengeUserId); - return allowSeparateProfileChallenge; + return mProfileUserId != UserHandle.USER_NULL + && mLockPatternUtils.isSeparateProfileChallengeAllowed(mProfileUserId); } @Override @@ -93,18 +104,18 @@ public class LockUnificationPreferenceController extends AbstractPreferenceContr @Override public boolean onPreferenceChange(Preference preference, Object value) { - if (Utils.startQuietModeDialogIfNecessary(mContext, mUm, mProfileChallengeUserId)) { + if (Utils.startQuietModeDialogIfNecessary(mContext, mUm, mProfileUserId)) { return false; } - if ((Boolean) value) { - final boolean compliantForDevice = - (mLockPatternUtils.getKeyguardStoredPasswordQuality(mProfileChallengeUserId) - >= DevicePolicyManager.PASSWORD_QUALITY_SOMETHING - && mLockPatternUtils.isSeparateProfileChallengeAllowedToUnify( - mProfileChallengeUserId)); - UnificationConfirmationDialog dialog = - UnificationConfirmationDialog.newInstance(compliantForDevice); - dialog.show(mHost); + final boolean useOneLock = (Boolean) value; + if (useOneLock) { + // Keep current device (personal) lock if the profile lock is empty or is not compliant + // with the policy on personal side. + mKeepDeviceLock = + mLockPatternUtils.getKeyguardStoredPasswordQuality(mProfileUserId) + < DevicePolicyManager.PASSWORD_QUALITY_SOMETHING + || !mDpm.isProfileActivePasswordSufficientForParent(mProfileUserId); + UnificationConfirmationDialog.newInstance(!mKeepDeviceLock).show(mHost); } else { final String title = mContext.getString(R.string.unlock_set_unlock_launch_picker_title); final ChooseLockSettingsHelper helper = @@ -122,12 +133,11 @@ public class LockUnificationPreferenceController extends AbstractPreferenceContr public void updateState(Preference preference) { if (mUnifyProfile != null) { final boolean separate = - mLockPatternUtils.isSeparateProfileChallengeEnabled(mProfileChallengeUserId); + mLockPatternUtils.isSeparateProfileChallengeEnabled(mProfileUserId); mUnifyProfile.setChecked(!separate); if (separate) { mUnifyProfile.setDisabledByAdmin(RestrictedLockUtils.checkIfRestrictionEnforced( - mContext, UserManager.DISALLOW_UNIFIED_PASSWORD, - mProfileChallengeUserId)); + mContext, UserManager.DISALLOW_UNIFIED_PASSWORD, mProfileUserId)); } } } @@ -141,7 +151,7 @@ public class LockUnificationPreferenceController extends AbstractPreferenceContr && resultCode == Activity.RESULT_OK) { mCurrentDevicePassword = data.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); - launchConfirmProfileLockForUnification(); + launchConfirmProfileLock(); return true; } else if (requestCode == UNIFY_LOCK_CONFIRM_PROFILE_REQUEST && resultCode == Activity.RESULT_OK) { @@ -155,7 +165,7 @@ public class LockUnificationPreferenceController extends AbstractPreferenceContr private void ununifyLocks() { final Bundle extras = new Bundle(); - extras.putInt(Intent.EXTRA_USER_ID, mProfileChallengeUserId); + extras.putInt(Intent.EXTRA_USER_ID, mProfileUserId); new SubSettingLauncher(mContext) .setDestination(ChooseLockGeneric.ChooseLockGenericFragment.class.getName()) .setTitleRes(R.string.lock_settings_picker_title_profile) @@ -164,54 +174,76 @@ public class LockUnificationPreferenceController extends AbstractPreferenceContr .launch(); } - void launchConfirmDeviceLockForUnification() { + /** Asks the user to confirm device lock (if there is one) and proceeds to ask profile lock. */ + private void launchConfirmDeviceAndProfileLock() { final String title = mContext.getString( R.string.unlock_set_unlock_launch_picker_title); final ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(mHost.getActivity(), mHost); if (!helper.launchConfirmationActivity( UNIFY_LOCK_CONFIRM_DEVICE_REQUEST, title, true, MY_USER_ID)) { - launchConfirmProfileLockForUnification(); + launchConfirmProfileLock(); } } - private void launchConfirmProfileLockForUnification() { + private void launchConfirmProfileLock() { final String title = mContext.getString( R.string.unlock_set_unlock_launch_picker_title_profile); final ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(mHost.getActivity(), mHost); if (!helper.launchConfirmationActivity( - UNIFY_LOCK_CONFIRM_PROFILE_REQUEST, title, true, mProfileChallengeUserId)) { + UNIFY_LOCK_CONFIRM_PROFILE_REQUEST, title, true, mProfileUserId)) { unifyLocks(); // TODO: update relevant prefs. // createPreferenceHierarchy(); } } + void startUnification() { + // If the device lock stays the same, only confirm profile lock. Otherwise confirm both. + if (mKeepDeviceLock) { + launchConfirmProfileLock(); + } else { + launchConfirmDeviceAndProfileLock(); + } + } + private void unifyLocks() { - int profileQuality = - mLockPatternUtils.getKeyguardStoredPasswordQuality(mProfileChallengeUserId); + if (mKeepDeviceLock) { + unifyKeepingDeviceLock(); + promptForNewDeviceLock(); + } else { + unifyKeepingWorkLock(); + } + mCurrentDevicePassword = null; + mCurrentProfilePassword = null; + } + + private void unifyKeepingWorkLock() { + final int profileQuality = + mLockPatternUtils.getKeyguardStoredPasswordQuality(mProfileUserId); + // PASSWORD_QUALITY_SOMETHING means pattern, everything above means PIN/password. if (profileQuality == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING) { mLockPatternUtils.saveLockPattern( LockPatternUtils.stringToPattern(mCurrentProfilePassword), mCurrentDevicePassword, MY_USER_ID); } else { mLockPatternUtils.saveLockPassword( - mCurrentProfilePassword, mCurrentDevicePassword, - profileQuality, MY_USER_ID); + mCurrentProfilePassword, mCurrentDevicePassword, profileQuality, MY_USER_ID); } - mLockPatternUtils.setSeparateProfileChallengeEnabled(mProfileChallengeUserId, false, + mLockPatternUtils.setSeparateProfileChallengeEnabled(mProfileUserId, false, mCurrentProfilePassword); final boolean profilePatternVisibility = - mLockPatternUtils.isVisiblePatternEnabled(mProfileChallengeUserId); + mLockPatternUtils.isVisiblePatternEnabled(mProfileUserId); mLockPatternUtils.setVisiblePatternEnabled(profilePatternVisibility, MY_USER_ID); - mCurrentDevicePassword = null; - mCurrentProfilePassword = null; } - void unifyUncompliantLocks() { - mLockPatternUtils.setSeparateProfileChallengeEnabled(mProfileChallengeUserId, false, + private void unifyKeepingDeviceLock() { + mLockPatternUtils.setSeparateProfileChallengeEnabled(mProfileUserId, false, mCurrentProfilePassword); + } + + private void promptForNewDeviceLock() { new SubSettingLauncher(mContext) .setDestination(ChooseLockGeneric.ChooseLockGenericFragment.class.getName()) .setTitleRes(R.string.lock_settings_picker_title) diff --git a/src/com/android/settings/security/SecuritySettings.java b/src/com/android/settings/security/SecuritySettings.java index 905b7093fce..ed6b4e921ca 100644 --- a/src/com/android/settings/security/SecuritySettings.java +++ b/src/com/android/settings/security/SecuritySettings.java @@ -98,13 +98,8 @@ public class SecuritySettings extends DashboardFragment { super.onActivityResult(requestCode, resultCode, data); } - void launchConfirmDeviceLockForUnification() { - use(LockUnificationPreferenceController.class) - .launchConfirmDeviceLockForUnification(); - } - - void unifyUncompliantLocks() { - use(LockUnificationPreferenceController.class).unifyUncompliantLocks(); + void startUnification() { + use(LockUnificationPreferenceController.class).startUnification(); } void updateUnificationPreference() { diff --git a/src/com/android/settings/security/UnificationConfirmationDialog.java b/src/com/android/settings/security/UnificationConfirmationDialog.java index 029e64f3fe3..95f252843cc 100644 --- a/src/com/android/settings/security/UnificationConfirmationDialog.java +++ b/src/com/android/settings/security/UnificationConfirmationDialog.java @@ -59,15 +59,8 @@ public class UnificationConfirmationDialog extends InstrumentedDialogFragment { .setPositiveButton( compliant ? R.string.lock_settings_profile_unification_dialog_confirm : R.string - .lock_settings_profile_unification_dialog_uncompliant_confirm, - (dialog, whichButton) -> { - if (compliant) { - parentFragment.launchConfirmDeviceLockForUnification(); - } else { - parentFragment.unifyUncompliantLocks(); - } - } - ) + .lock_settings_profile_unification_dialog_uncompliant_confirm, + (dialog, whichButton) -> parentFragment.startUnification()) .setNegativeButton(R.string.cancel, null) .create(); } diff --git a/src/com/android/settings/slices/CustomSliceManager.java b/src/com/android/settings/slices/CustomSliceManager.java new file mode 100644 index 00000000000..f3df3c19d55 --- /dev/null +++ b/src/com/android/settings/slices/CustomSliceManager.java @@ -0,0 +1,88 @@ +/* + * 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.slices; + +import android.content.Context; +import android.net.Uri; +import android.util.ArrayMap; + +import java.util.Map; + +/** + * Manages custom {@link androidx.slice.Slice Slices}, which are all Slices not backed by + * preferences. + *

+ * By default, all Slices in Settings should be built by a + *

+ */ +public class CustomSliceManager { + + protected final Map> mUriMap; + + private final Context mContext; + + public CustomSliceManager(Context context) { + mContext = context; + mUriMap = new ArrayMap<>(); + addSlices(); + } + + /** + * Return a {@link CustomSliceable} associated to the Uri. + *

+ * Do not change this method signature to accommodate for a special-case slicable - a context is + * the only thing that should be needed to create the object. + */ + public CustomSliceable getSliceableFromUri(Uri uri) { + final Class clazz = mUriMap.get(uri); + + if (clazz == null) { + throw new IllegalArgumentException("No Slice found for uri: " + uri); + } + + return CustomSliceable.createInstance(mContext, clazz); + } + + /** + * Return a {@link CustomSliceable} associated to the Action. + *

+ * Do not change this method signature to accommodate for a special-case sliceable - a context + * is the only thing that should be needed to create the object. + */ + public CustomSliceable getSliceableFromIntentAction(String action) { + return getSliceableFromUri(Uri.parse(action)); + } + + /** + * Returns {@code true} if {@param uri} is a valid Slice Uri handled by + * {@link CustomSliceManager}. + */ + public boolean isValidUri(Uri uri) { + return mUriMap.containsKey(uri); + } + + /** + * Returns {@code true} if {@param action} is a valid intent action handled by + * {@link CustomSliceManager}. + */ + public boolean isValidAction(String action) { + return isValidUri(Uri.parse(action)); + } + + private void addSlices() { + } +} \ No newline at end of file diff --git a/src/com/android/settings/slices/CustomSliceable.java b/src/com/android/settings/slices/CustomSliceable.java new file mode 100644 index 00000000000..afe7170c508 --- /dev/null +++ b/src/com/android/settings/slices/CustomSliceable.java @@ -0,0 +1,102 @@ +/* + * 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.slices; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import androidx.slice.Slice; + + +/** + * Common functions for custom Slices. + *

+ * A template for all Settings slices which are not represented by a Preference. By + * standardizing the methods used by the Slice helpers, we can use generically take actions + * rather than maintaining a list of all of the custom slices every time we reference Slices in + * Settings. + *

+ * By default, all Slices in Settings should be built through Preference Controllers extending + * {@link com.android.settings.core.BasePreferenceController}, which are automatically piped + * into Settings-Slices infrastructure. Cases where you should implement this interface are: + *

+ *

+ * Note that if your UI is supported because the Preference is not backed by a + * {@link com.android.settings.dashboard.DashboardFragment}, then you should first convert the + * existing fragment into a dashboard fragment, and then extend + * {@link com.android.settings.core.BasePreferenceController}. + *

+ * If you implement this interface, you should add your Slice to {@link CustomSliceManager}. + */ +public interface CustomSliceable { + + /** + * @return an complete instance of the {@link Slice}. + */ + Slice getSlice(Context context); + + /** + * @return a {@link android.content.ContentResolver#SCHEME_CONTENT content} {@link Uri} which + * backs the {@link Slice} returned by {@link #getSlice(Context)}. + */ + Uri getUri(); + + /** + * Handles the actions sent by the {@link Intent intents} bound to the {@link Slice} returned by + * {@link #getSlice(Context)}. + * + * @param intent which has the action taken on a {@link Slice}. + */ + void onNotifyChange(Intent intent); + + /** + * Settings Slices which can represent components that are updatable by the framework should + * listen to changes matched to the {@link IntentFilter} returned here. + * + * @return an {@link IntentFilter} for updates related to the {@link Slice} returned by + * {@link #getSlice(Context)}. + */ + default IntentFilter getIntentFilter() { + return null; + } + + /** + * Build an instance of a {@link CustomSliceable} which has a {@link Context}-only constructor. + */ + static CustomSliceable createInstance(Context context, Class sliceableClass) { + try { + //final Class clazz = Class.forName(sliceableClassName); + final Constructor sliceable = + sliceableClass.getConstructor(Context.class); + final Object[] params = new Object[]{context}; + return sliceable.newInstance(params); + } catch (NoSuchMethodException | InstantiationException | + IllegalArgumentException | InvocationTargetException | IllegalAccessException e) { + throw new IllegalStateException( + "Invalid sliceable class: " + sliceableClass, e); + } + } +} \ No newline at end of file diff --git a/src/com/android/settings/slices/SettingsSliceProvider.java b/src/com/android/settings/slices/SettingsSliceProvider.java index 9cde91a8558..da705c94dd6 100644 --- a/src/com/android/settings/slices/SettingsSliceProvider.java +++ b/src/com/android/settings/slices/SettingsSliceProvider.java @@ -32,15 +32,15 @@ import android.util.KeyValueListParser; import android.util.Log; import android.util.Pair; -import com.android.settings.bluetooth.BluetoothSliceBuilder; -import com.android.settings.core.BasePreferenceController; import com.android.settings.flashlight.FlashlightSliceBuilder; import com.android.settings.location.LocationSliceBuilder; import com.android.settings.mobilenetwork.Enhanced4gLteSliceHelper; import com.android.settings.notification.ZenModeSliceBuilder; import com.android.settings.overlay.FeatureFactory; +import com.android.settings.core.BasePreferenceController; import com.android.settings.wifi.WifiSliceBuilder; import com.android.settings.wifi.calling.WifiCallingSliceHelper; +import com.android.settings.bluetooth.BluetoothSliceBuilder; import com.android.settingslib.SliceBroadcastRelay; import com.android.settingslib.utils.ThreadUtils; @@ -113,11 +113,15 @@ public class SettingsSliceProvider extends SliceProvider { public static final String EXTRA_SLICE_PLATFORM_DEFINED = "com.android.settings.slice.extra.platform"; + @VisibleForTesting + CustomSliceManager mCustomSliceManager; + @VisibleForTesting SlicesDatabaseAccessor mSlicesDatabaseAccessor; @VisibleForTesting Map mSliceWeakDataCache; + @VisibleForTesting Map mSliceDataCache; @@ -135,6 +139,8 @@ public class SettingsSliceProvider extends SliceProvider { mSlicesDatabaseAccessor = new SlicesDatabaseAccessor(getContext()); mSliceDataCache = new ConcurrentHashMap<>(); mSliceWeakDataCache = new WeakHashMap<>(); + mCustomSliceManager = FeatureFactory.getFactory( + getContext()).getSlicesFeatureProvider().getCustomSliceManager(getContext()); return true; } @@ -151,6 +157,15 @@ public class SettingsSliceProvider extends SliceProvider { @Override public void onSlicePinned(Uri sliceUri) { + if (mCustomSliceManager.isValidUri(sliceUri)) { + final CustomSliceable sliceable = mCustomSliceManager.getSliceableFromUri(sliceUri); + final IntentFilter filter = sliceable.getIntentFilter(); + if (filter != null) { + registerIntentToUri(filter, sliceUri); + } + return; + } + if (WifiSliceBuilder.WIFI_URI.equals(sliceUri)) { registerIntentToUri(WifiSliceBuilder.INTENT_FILTER, sliceUri); mRegisteredUris.add(sliceUri); @@ -162,7 +177,7 @@ public class SettingsSliceProvider extends SliceProvider { registerIntentToUri(BluetoothSliceBuilder.INTENT_FILTER, sliceUri); return; } else if (FlashlightSliceBuilder.FLASHLIGHT_URI.equals(sliceUri)) { - registerIntentToUri(FlashlightSliceBuilder.INTENT_FILTER , sliceUri); + registerIntentToUri(FlashlightSliceBuilder.INTENT_FILTER, sliceUri); mRegisteredUris.add(sliceUri); return; } @@ -197,8 +212,14 @@ public class SettingsSliceProvider extends SliceProvider { return null; } - // If adding a new Slice, do not directly match Slice URIs. - // Use {@link SlicesDatabaseAccessor}. + // Before adding a slice to {@link CustomSliceManager}, please get approval + // from the Settings team. + if (mCustomSliceManager.isValidUri(sliceUri)) { + final CustomSliceable sliceable = mCustomSliceManager.getSliceableFromUri( + sliceUri); + return sliceable.getSlice(getContext()); + } + if (WifiCallingSliceHelper.WIFI_CALLING_URI.equals(sliceUri)) { return FeatureFactory.getFactory(getContext()) .getSlicesFeatureProvider() @@ -433,4 +454,4 @@ public class SettingsSliceProvider extends SliceProvider { } return new String[0]; } -} +} \ No newline at end of file diff --git a/src/com/android/settings/slices/SliceBroadcastReceiver.java b/src/com/android/settings/slices/SliceBroadcastReceiver.java index 7d50410731f..a44a2cd6e0e 100644 --- a/src/com/android/settings/slices/SliceBroadcastReceiver.java +++ b/src/com/android/settings/slices/SliceBroadcastReceiver.java @@ -71,6 +71,15 @@ public class SliceBroadcastReceiver extends BroadcastReceiver { final boolean isPlatformSlice = intent.getBooleanExtra(EXTRA_SLICE_PLATFORM_DEFINED, false /* default */); + final CustomSliceManager mCustomSliceManager = FeatureFactory.getFactory( + context).getSlicesFeatureProvider().getCustomSliceManager(context); + if (mCustomSliceManager.isValidAction(action)) { + final CustomSliceable sliceable = + mCustomSliceManager.getSliceableFromIntentAction(action); + sliceable.onNotifyChange(intent); + return; + } + switch (action) { case ACTION_TOGGLE_CHANGED: final boolean isChecked = intent.getBooleanExtra(Slice.EXTRA_TOGGLE_STATE, false); diff --git a/src/com/android/settings/slices/SlicesFeatureProvider.java b/src/com/android/settings/slices/SlicesFeatureProvider.java index 8395a58dc16..5940aa452fb 100644 --- a/src/com/android/settings/slices/SlicesFeatureProvider.java +++ b/src/com/android/settings/slices/SlicesFeatureProvider.java @@ -28,6 +28,8 @@ public interface SlicesFeatureProvider { */ void indexSliceData(Context context); + CustomSliceManager getCustomSliceManager(Context context); + /** * Gets new WifiCallingSliceHelper object */ diff --git a/src/com/android/settings/slices/SlicesFeatureProviderImpl.java b/src/com/android/settings/slices/SlicesFeatureProviderImpl.java index 988bcfe2273..fc2298cea69 100644 --- a/src/com/android/settings/slices/SlicesFeatureProviderImpl.java +++ b/src/com/android/settings/slices/SlicesFeatureProviderImpl.java @@ -13,23 +13,32 @@ public class SlicesFeatureProviderImpl implements SlicesFeatureProvider { private SlicesIndexer mSlicesIndexer; private SliceDataConverter mSliceDataConverter; + private CustomSliceManager mCustomSliceManager; @Override public SlicesIndexer getSliceIndexer(Context context) { if (mSlicesIndexer == null) { - mSlicesIndexer = new SlicesIndexer(context); + mSlicesIndexer = new SlicesIndexer(context.getApplicationContext()); } return mSlicesIndexer; } @Override public SliceDataConverter getSliceDataConverter(Context context) { - if(mSliceDataConverter == null) { + if (mSliceDataConverter == null) { mSliceDataConverter = new SliceDataConverter(context.getApplicationContext()); } return mSliceDataConverter; } + @Override + public CustomSliceManager getCustomSliceManager(Context context) { + if (mCustomSliceManager == null) { + mCustomSliceManager = new CustomSliceManager(context.getApplicationContext()); + } + return mCustomSliceManager; + } + @Override public void indexSliceDataAsync(Context context) { SlicesIndexer indexer = getSliceIndexer(context); diff --git a/tests/robotests/src/com/android/settings/datausage/DataUsageSummaryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datausage/DataUsageSummaryPreferenceControllerTest.java index d8e447da8c7..b6eb603cd13 100644 --- a/tests/robotests/src/com/android/settings/datausage/DataUsageSummaryPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/datausage/DataUsageSummaryPreferenceControllerTest.java @@ -354,7 +354,7 @@ public class DataUsageSummaryPreferenceControllerTest { mActivity, null, null, null); final SubscriptionInfo subInfo = new SubscriptionInfo(0, "123456", 0, "name", "carrier", - 0, 0, "number", 0, null, 123, 456, "ZX"); + 0, 0, "number", 0, null, "123", "456", "ZX"); when(mSubscriptionManager.getDefaultDataSubscriptionInfo()).thenReturn(subInfo); assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); } diff --git a/tests/robotests/src/com/android/settings/mobilenetwork/Enhanced4gLteSliceHelperTest.java b/tests/robotests/src/com/android/settings/mobilenetwork/Enhanced4gLteSliceHelperTest.java index 9c719cd8153..434a89ddf90 100644 --- a/tests/robotests/src/com/android/settings/mobilenetwork/Enhanced4gLteSliceHelperTest.java +++ b/tests/robotests/src/com/android/settings/mobilenetwork/Enhanced4gLteSliceHelperTest.java @@ -20,6 +20,7 @@ import static android.app.slice.Slice.EXTRA_TOGGLE_STATE; import static android.app.slice.Slice.HINT_TITLE; import static android.app.slice.SliceItem.FORMAT_TEXT; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; @@ -37,6 +38,7 @@ import com.android.settings.R; import com.android.settings.slices.SettingsSliceProvider; import com.android.settings.slices.SliceBroadcastReceiver; import com.android.settings.slices.SlicesFeatureProvider; +import com.android.settings.slices.CustomSliceManager; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; @@ -61,13 +63,13 @@ import androidx.slice.widget.SliceLiveData; @RunWith(SettingsRobolectricTestRunner.class) public class Enhanced4gLteSliceHelperTest { - private Context mContext; @Mock private CarrierConfigManager mMockCarrierConfigManager; @Mock private ImsManager mMockImsManager; + private Context mContext; private FakeEnhanced4gLteSliceHelper mEnhanced4gLteSliceHelper; private SettingsSliceProvider mProvider; private SliceBroadcastReceiver mReceiver; @@ -79,16 +81,20 @@ public class Enhanced4gLteSliceHelperTest { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); + mFeatureFactory = FakeFeatureFactory.setupForTest(); + mSlicesFeatureProvider = mFeatureFactory.getSlicesFeatureProvider(); + + when(mSlicesFeatureProvider.getCustomSliceManager(any(Context.class))) + .thenReturn(new CustomSliceManager(mContext)); + //setup for SettingsSliceProvider tests mProvider = spy(new SettingsSliceProvider()); doReturn(mContext).when(mProvider).getContext(); + mProvider.onCreateSliceProvider(); //setup for SliceBroadcastReceiver test mReceiver = spy(new SliceBroadcastReceiver()); - mFeatureFactory = FakeFeatureFactory.setupForTest(); - mSlicesFeatureProvider = mFeatureFactory.getSlicesFeatureProvider(); - // Prevent crash in SliceMetadata. Resources resources = spy(mContext.getResources()); doReturn(60).when(resources).getDimensionPixelSize(anyInt()); diff --git a/tests/robotests/src/com/android/settings/security/LockUnificationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/security/LockUnificationPreferenceControllerTest.java index 7dcd71cccc9..53046c9ef59 100644 --- a/tests/robotests/src/com/android/settings/security/LockUnificationPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/security/LockUnificationPreferenceControllerTest.java @@ -17,11 +17,11 @@ package com.android.settings.security; import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.when; import android.content.Context; -import android.os.UserHandle; import android.os.UserManager; import com.android.internal.widget.LockPatternUtils; @@ -35,7 +35,6 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RuntimeEnvironment; import org.robolectric.shadows.ShadowApplication; -import org.robolectric.util.ReflectionHelpers; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; @@ -54,7 +53,6 @@ public class LockUnificationPreferenceControllerTest { @Mock private SecuritySettings mHost; - private FakeFeatureFactory mFeatureFactory; private Context mContext; private LockUnificationPreferenceController mController; private Preference mPreference; @@ -66,10 +64,12 @@ public class LockUnificationPreferenceControllerTest { ShadowApplication.getInstance().setSystemService(Context.USER_SERVICE, mUm); when(mUm.getProfileIdsWithDisabled(anyInt())).thenReturn(new int[] {FAKE_PROFILE_USER_ID}); - mFeatureFactory = FakeFeatureFactory.setupForTest(); - when(mFeatureFactory.securityFeatureProvider.getLockPatternUtils(mContext)) + final FakeFeatureFactory featureFactory = FakeFeatureFactory.setupForTest(); + when(featureFactory.securityFeatureProvider.getLockPatternUtils(mContext)) .thenReturn(mLockPatternUtils); + } + private void init() { mController = new LockUnificationPreferenceController(mContext, mHost); when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); mPreference = new Preference(mContext); @@ -77,7 +77,8 @@ public class LockUnificationPreferenceControllerTest { @Test public void isAvailable_noProfile_false() { - ReflectionHelpers.setField(mController, "mProfileChallengeUserId", UserHandle.USER_NULL); + when(mUm.getProfileIdsWithDisabled(anyInt())).thenReturn(new int[0]); + init(); assertThat(mController.isAvailable()).isFalse(); } @@ -85,6 +86,7 @@ public class LockUnificationPreferenceControllerTest { @Test public void isAvailable_separateChallengeNotAllowed_false() { when(mLockPatternUtils.isSeparateProfileChallengeAllowed(anyInt())).thenReturn(false); + init(); assertThat(mController.isAvailable()).isFalse(); } @@ -92,6 +94,7 @@ public class LockUnificationPreferenceControllerTest { @Test public void isAvailable_separateChallengeAllowed_true() { when(mLockPatternUtils.isSeparateProfileChallengeAllowed(anyInt())).thenReturn(true); + init(); assertThat(mController.isAvailable()).isTrue(); } diff --git a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java index c72e9f6c542..ea2f2cac6e7 100644 --- a/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java +++ b/tests/robotests/src/com/android/settings/slices/SettingsSliceProviderTest.java @@ -109,6 +109,7 @@ public class SettingsSliceProviderTest { mProvider.mSliceWeakDataCache = new HashMap<>(); mProvider.mSliceDataCache = new HashMap<>(); mProvider.mSlicesDatabaseAccessor = new SlicesDatabaseAccessor(mContext); + mProvider.mCustomSliceManager = new CustomSliceManager(mContext); when(mProvider.getContext()).thenReturn(mContext); mDb = SlicesDatabaseHelper.getInstance(mContext).getWritableDatabase(); diff --git a/tests/robotests/src/com/android/settings/slices/SliceBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/slices/SliceBroadcastReceiverTest.java index d57834851f9..955cdf107c6 100644 --- a/tests/robotests/src/com/android/settings/slices/SliceBroadcastReceiverTest.java +++ b/tests/robotests/src/com/android/settings/slices/SliceBroadcastReceiverTest.java @@ -18,12 +18,15 @@ package com.android.settings.slices; import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.app.slice.Slice; import android.content.ContentResolver; @@ -82,6 +85,8 @@ public class SliceBroadcastReceiverTest { mSearchFeatureProvider = new SearchFeatureProviderImpl(); mFakeFeatureFactory = FakeFeatureFactory.setupForTest(); mFakeFeatureFactory.searchFeatureProvider = mSearchFeatureProvider; + when(mFakeFeatureFactory.slicesFeatureProvider.getCustomSliceManager(any())) + .thenReturn(new CustomSliceManager(mContext)); mLoggingNameArgumentCatpor = ArgumentCaptor.forClass(Pair.class); mLoggingValueArgumentCatpor = ArgumentCaptor.forClass(Pair.class); } diff --git a/tests/robotests/src/com/android/settings/slices/SpecialCaseSliceManagerTest.java b/tests/robotests/src/com/android/settings/slices/SpecialCaseSliceManagerTest.java new file mode 100644 index 00000000000..ab226f9db54 --- /dev/null +++ b/tests/robotests/src/com/android/settings/slices/SpecialCaseSliceManagerTest.java @@ -0,0 +1,137 @@ +/* + * 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.slices; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; +import android.provider.SettingsSlicesContract; + +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RuntimeEnvironment; + +import androidx.slice.Slice; + +@RunWith(SettingsRobolectricTestRunner.class) +public class SpecialCaseSliceManagerTest { + + private Context mContext; + + private CustomSliceManager mCustomSliceManager; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mCustomSliceManager = spy(new CustomSliceManager(mContext)); + mCustomSliceManager.mUriMap.clear(); + mCustomSliceManager.mUriMap.put(FakeSliceable.URI, FakeSliceable.class); + } + + @Test + public void getSliceableFromUri_returnsCorrectObject() { + final CustomSliceable sliceable = mCustomSliceManager.getSliceableFromUri( + FakeSliceable.URI); + + assertThat(sliceable).isInstanceOf(FakeSliceable.class); + } + + @Test + public void getSliceableFromIntentAction_returnsCorrectObject() { + final CustomSliceable sliceable = + mCustomSliceManager.getSliceableFromIntentAction(FakeSliceable.URI.toString()); + + assertThat(sliceable).isInstanceOf(FakeSliceable.class); + } + + @Test + public void isValidUri_validUri_returnsTrue() { + final boolean isValidUri = mCustomSliceManager.isValidUri(FakeSliceable.URI); + + assertThat(isValidUri).isTrue(); + } + + @Test + public void isValidUri_invalidUri_returnsFalse() { + final boolean isValidUri = mCustomSliceManager.isValidUri(null); + + assertThat(isValidUri).isFalse(); + } + + @Test + public void isValidAction_validActions_returnsTrue() { + final boolean isValidAction = + mCustomSliceManager.isValidAction(FakeSliceable.URI.toString()); + + assertThat(isValidAction).isTrue(); + } + + @Test + public void isValidAction_invalidAction_returnsFalse() { + final boolean isValidAction = mCustomSliceManager.isValidAction("action"); + + assertThat(isValidAction).isFalse(); + } + + static class FakeSliceable implements CustomSliceable { + + static final String KEY = "magic key of khazad dum"; + + static final Uri URI = new Uri.Builder() + .scheme(ContentResolver.SCHEME_CONTENT) + .authority(SettingsSliceProvider.SLICE_AUTHORITY) + .appendPath(SettingsSlicesContract.PATH_SETTING_ACTION) + .appendPath(KEY) + .build(); + + static final Slice SLICE = new Slice.Builder(URI).build(); + + static boolean backingData = false; + + public FakeSliceable(Context context) {} + + @Override + public Slice getSlice(Context context) { + return SLICE; + } + + @Override + public Uri getUri() { + return URI; + } + + @Override + public void onNotifyChange(Intent intent) { + backingData = !backingData; + } + + @Override + public IntentFilter getIntentFilter() { + return new IntentFilter(); + } + } +} diff --git a/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSliceHelperTest.java b/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSliceHelperTest.java index cdc8ecbfcf9..c9c9abb7383 100644 --- a/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSliceHelperTest.java +++ b/tests/robotests/src/com/android/settings/wifi/calling/WifiCallingSliceHelperTest.java @@ -20,6 +20,8 @@ import static android.app.slice.Slice.EXTRA_TOGGLE_STATE; import static android.app.slice.Slice.HINT_TITLE; import static android.app.slice.SliceItem.FORMAT_TEXT; 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.eq; import static org.mockito.Mockito.doReturn; @@ -40,6 +42,7 @@ import com.android.settings.slices.SettingsSliceProvider; import com.android.settings.slices.SliceBroadcastReceiver; import com.android.settings.slices.SliceData; import com.android.settings.slices.SlicesFeatureProvider; +import com.android.settings.slices.CustomSliceManager; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; @@ -88,12 +91,15 @@ public class WifiCallingSliceHelperTest { //setup for SettingsSliceProvider tests mProvider = spy(new SettingsSliceProvider()); doReturn(mContext).when(mProvider).getContext(); + mProvider.onCreateSliceProvider(); //setup for SliceBroadcastReceiver test mReceiver = spy(new SliceBroadcastReceiver()); mFeatureFactory = FakeFeatureFactory.setupForTest(); mSlicesFeatureProvider = mFeatureFactory.getSlicesFeatureProvider(); + when(mSlicesFeatureProvider.getCustomSliceManager(any(Context.class))) + .thenReturn(new CustomSliceManager(mContext)); // Prevent crash in SliceMetadata. Resources resources = spy(mContext.getResources());