diff options
Diffstat (limited to 'src/com')
108 files changed, 2633 insertions, 1186 deletions
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index 7dd5fe40e4..12f63ea226 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -36,10 +36,8 @@ import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; import android.text.TextUtils; -import android.util.FeatureFlagUtils; import android.util.Log; import android.view.View; -import android.view.Window; import android.widget.Button; import androidx.annotation.Nullable; @@ -55,7 +53,6 @@ import androidx.preference.PreferenceManager; import com.android.internal.util.ArrayUtils; import com.android.settings.Settings.WifiSettingsActivity; import com.android.settings.applications.manageapplications.ManageApplications; -import com.android.settings.core.FeatureFlags; import com.android.settings.core.OnActivityResultListener; import com.android.settings.core.SettingsBaseActivity; import com.android.settings.core.SubSettingLauncher; @@ -70,7 +67,6 @@ import com.android.settingslib.core.instrumentation.SharedPreferencesLogger; import com.android.settingslib.development.DevelopmentSettingsEnabler; import com.android.settingslib.drawer.DashboardCategory; -import com.google.android.material.transition.platform.MaterialSharedAxis; import com.google.android.setupcompat.util.WizardManagerHelper; import java.util.ArrayList; @@ -689,7 +685,7 @@ public class SettingsActivity extends SettingsBaseActivity if (somethingChanged) { Log.d(LOG_TAG, "Enabled state changed for some tiles, reloading all categories " + changedList.toString()); - updateCategories(); + mCategoryMixin.updateCategories(); } else { Log.d(LOG_TAG, "No enabled state changed, skipping updateCategory call"); } diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index 4f0515c02d..708dbeddec 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -77,7 +77,6 @@ import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.Profile; import android.provider.ContactsContract.RawContacts; -import android.provider.Settings; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.text.Spannable; @@ -164,9 +163,6 @@ public final class Utils extends com.android.settingslib.Utils { public static final String PROPERTY_HIBERNATION_TARGETS_PRE_S_APPS = "app_hibernation_targets_pre_s_apps"; - /** Whether or not Settings Shared Axis transition is enabled */ - public static final String SETTINGS_SHARED_AXIS_ENABLED = "settings_shared_axis_enabled"; - /** * Finds a matching activity for a preference's intent. If a matching * activity is not found, it will remove the preference. @@ -1225,9 +1221,4 @@ public final class Utils extends com.android.settingslib.Utils { public static boolean isProviderModelEnabled(Context context) { return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_PROVIDER_MODEL); } - - public static boolean isPageTransitionEnabled(Context context) { - return Settings.Global.getInt(context.getContentResolver(), - SETTINGS_SHARED_AXIS_ENABLED, 0) == 1; - } } diff --git a/src/com/android/settings/accessibility/AccessibilitySearchFeatureProvider.java b/src/com/android/settings/accessibility/AccessibilitySearchFeatureProvider.java new file mode 100644 index 0000000000..6aa8c841ed --- /dev/null +++ b/src/com/android/settings/accessibility/AccessibilitySearchFeatureProvider.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2021 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.accessibility; + +import android.content.Context; + +import com.android.settingslib.search.SearchIndexableRaw; + +import java.util.List; + +/** + * Provider for Accessibility Search related features. + */ +public interface AccessibilitySearchFeatureProvider { + + /** + * Returns a list of raw data for indexing. See {@link SearchIndexableRaw} + * + * @param context a valid context {@link Context} instance + * @return a list of {@link SearchIndexableRaw} references. Can be null. + */ + List<SearchIndexableRaw> getSearchIndexableRawData(Context context); +} diff --git a/src/com/android/settings/accessibility/AccessibilitySearchFeatureProviderImpl.java b/src/com/android/settings/accessibility/AccessibilitySearchFeatureProviderImpl.java new file mode 100644 index 0000000000..c358af11d0 --- /dev/null +++ b/src/com/android/settings/accessibility/AccessibilitySearchFeatureProviderImpl.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 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.accessibility; + +import android.content.Context; + +import com.android.settingslib.search.SearchIndexableRaw; + +import java.util.List; + +/** + * Provider implementation for Accessibility Search related features. + */ +public class AccessibilitySearchFeatureProviderImpl implements AccessibilitySearchFeatureProvider { + + @Override + public List<SearchIndexableRaw> getSearchIndexableRawData(Context context) { + return null; + } +} diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java index d4db3953f7..78bea0f00a 100644 --- a/src/com/android/settings/accessibility/AccessibilitySettings.java +++ b/src/com/android/settings/accessibility/AccessibilitySettings.java @@ -50,12 +50,14 @@ import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.accessibility.AccessibilityUtil.AccessibilityServiceFragmentType; import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.RestrictedPreference; import com.android.settingslib.accessibility.AccessibilityUtils; import com.android.settingslib.search.SearchIndexable; +import com.android.settingslib.search.SearchIndexableRaw; import java.util.ArrayList; import java.util.Collection; @@ -494,7 +496,15 @@ public class AccessibilitySettings extends DashboardFragment { } public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = - new BaseSearchIndexProvider(R.xml.accessibility_settings); + new BaseSearchIndexProvider(R.xml.accessibility_settings) { + @Override + public List<SearchIndexableRaw> getRawDataToIndex(Context context, + boolean enabled) { + return FeatureFactory.getFactory(context) + .getAccessibilitySearchFeatureProvider().getSearchIndexableRawData( + context); + } + }; /** * This class helps setup RestrictedPreference. @@ -559,7 +569,6 @@ public class AccessibilitySettings extends DashboardFragment { setRestrictedPreferenceEnabled(preference, packageName, serviceAllowed, serviceEnabled); - final String prefKey = preference.getKey(); final int imageRes = info.getAnimatedImageRes(); final CharSequence description = getServiceDescription(mContext, info, diff --git a/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java b/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java index 709e1657b7..127c7c68a3 100644 --- a/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java +++ b/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java @@ -17,6 +17,7 @@ package com.android.settings.accessibility; import static com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums; +import static com.android.settings.accessibility.ToggleFeaturePreferenceFragment.KEY_GENERAL_CATEGORY; import android.app.Dialog; import android.app.settings.SettingsEnums; @@ -34,11 +35,13 @@ import android.view.ViewGroup; import android.view.accessibility.AccessibilityManager; import android.widget.CheckBox; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; import com.android.settings.R; -import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.dashboard.DashboardFragment; import com.android.settings.utils.LocaleUtils; import com.google.android.setupcompat.util.WizardManagerHelper; @@ -50,7 +53,7 @@ import java.util.Locale; /** * Base class for accessibility fragments shortcut functions and dialog management. */ -public abstract class AccessibilityShortcutPreferenceFragment extends SettingsPreferenceFragment +public abstract class AccessibilityShortcutPreferenceFragment extends DashboardFragment implements ShortcutPreference.OnClickCallback { private static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference"; protected static final String KEY_SAVED_USER_SHORTCUT_TYPE = "shortcut_type"; @@ -88,6 +91,10 @@ public abstract class AccessibilityShortcutPreferenceFragment extends SettingsPr setPreferenceScreen(preferenceScreen); } + if (showGeneralCategory()) { + initGeneralCategory(); + } + final List<String> shortcutFeatureKeys = new ArrayList<>(); shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS); shortcutFeatureKeys.add(Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE); @@ -151,6 +158,7 @@ public abstract class AccessibilityShortcutPreferenceFragment extends SettingsPr @Override public Dialog onCreateDialog(int dialogId) { + final Dialog dialog; switch (dialogId) { case DialogEnums.EDIT_SHORTCUT: final CharSequence dialogTitle = getPrefContext().getString( @@ -158,11 +166,17 @@ public abstract class AccessibilityShortcutPreferenceFragment extends SettingsPr final int dialogType = WizardManagerHelper.isAnySetupWizard(getIntent()) ? AccessibilityDialogUtils.DialogType.EDIT_SHORTCUT_GENERIC_SUW : AccessibilityDialogUtils.DialogType.EDIT_SHORTCUT_GENERIC; - final Dialog dialog = AccessibilityDialogUtils.showEditShortcutDialog( + dialog = AccessibilityDialogUtils.showEditShortcutDialog( getPrefContext(), dialogType, dialogTitle, this::callOnAlertDialogCheckboxClicked); setupEditShortcutDialog(dialog); return dialog; + case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL: + dialog = AccessibilityGestureNavigationTutorial + .createAccessibilityTutorialDialog(getPrefContext(), + getUserShortcutTypes()); + dialog.setCanceledOnTouchOutside(false); + return dialog; default: throw new IllegalArgumentException("Unsupported dialogId " + dialogId); } @@ -173,6 +187,8 @@ public abstract class AccessibilityShortcutPreferenceFragment extends SettingsPr switch (dialogId) { case DialogEnums.EDIT_SHORTCUT: return SettingsEnums.DIALOG_ACCESSIBILITY_SERVICE_EDIT_SHORTCUT; + case DialogEnums.LAUNCH_ACCESSIBILITY_TUTORIAL: + return SettingsEnums.DIALOG_ACCESSIBILITY_TUTORIAL; default: return SettingsEnums.ACTION_UNKNOWN; } @@ -202,6 +218,11 @@ public abstract class AccessibilityShortcutPreferenceFragment extends SettingsPr mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); } + /** + * Overrides to return specific shortcut preference key + * + * @return String The specific shortcut preference key + */ protected String getShortcutPreferenceKey() { return KEY_SHORTCUT_PREFERENCE; } @@ -238,6 +259,13 @@ public abstract class AccessibilityShortcutPreferenceFragment extends SettingsPr return value; } + /** + * Returns the shortcut type list which has been checked by user. + */ + protected int getUserShortcutTypes() { + return AccessibilityUtil.getUserShortcutTypesFromSettings(getPrefContext(), + getComponentName()); + }; /** * This method will be invoked when a button in the edit shortcut dialog is clicked. @@ -260,6 +288,15 @@ public abstract class AccessibilityShortcutPreferenceFragment extends SettingsPr } @VisibleForTesting + void initGeneralCategory() { + final PreferenceCategory generalCategory = new PreferenceCategory(getPrefContext()); + generalCategory.setKey(KEY_GENERAL_CATEGORY); + generalCategory.setTitle(getGeneralCategoryDescription(null)); + + getPreferenceScreen().addPreference(generalCategory); + } + + @VisibleForTesting void saveNonEmptyUserShortcutType(int type) { if (type == AccessibilityUtil.UserShortcutType.EMPTY) { return; @@ -270,6 +307,28 @@ public abstract class AccessibilityShortcutPreferenceFragment extends SettingsPr PreferredShortcuts.saveUserShortcutType(getPrefContext(), shortcut); } + /** + * Overrides to return customized description for general category above shortcut + * + * @return CharSequence The customized description for general category + */ + protected CharSequence getGeneralCategoryDescription(@Nullable CharSequence title) { + if (title == null || title.toString().isEmpty()) { + // Return default 'Options' string for category + return getContext().getString(R.string.accessibility_screen_option); + } + return title; + } + + /** + * Overrides to determinate if showing additional category description above shortcut + * + * @return boolean true to show category, false otherwise. + */ + protected boolean showGeneralCategory() { + return false; + } + private void setDialogTextAreaClickListener(View dialogView, CheckBox checkBox) { final View dialogTextArea = dialogView.findViewById(R.id.container); dialogTextArea.setOnClickListener(v -> checkBox.toggle()); diff --git a/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java b/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java index fe08d6b5bd..98090ac1db 100644 --- a/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java +++ b/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java @@ -78,10 +78,12 @@ public class LaunchAccessibilityActivityPreferenceFragment extends ToggleFeature // Settings animated image. final int animatedImageRes = arguments.getInt( AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES); - mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) - .authority(mComponentName.getPackageName()) - .appendPath(String.valueOf(animatedImageRes)) - .build(); + if (animatedImageRes > 0) { + mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) + .authority(mComponentName.getPackageName()) + .appendPath(String.valueOf(animatedImageRes)) + .build(); + } // Settings html description. mHtmlDescription = arguments.getCharSequence(AccessibilitySettings.EXTRA_HTML_DESCRIPTION); diff --git a/src/com/android/settings/accessibility/ReduceBrightColorsIntensityPreferenceController.java b/src/com/android/settings/accessibility/ReduceBrightColorsIntensityPreferenceController.java index 7e6b42daac..156b942176 100644 --- a/src/com/android/settings/accessibility/ReduceBrightColorsIntensityPreferenceController.java +++ b/src/com/android/settings/accessibility/ReduceBrightColorsIntensityPreferenceController.java @@ -28,6 +28,7 @@ import com.android.settings.widget.SeekBarPreference; /** PreferenceController for feature intensity. */ public class ReduceBrightColorsIntensityPreferenceController extends SliderPreferenceController { + private static final int INVERSE_PERCENTAGE_BASE = 100; private final ColorDisplayManager mColorDisplayManager; public ReduceBrightColorsIntensityPreferenceController(Context context, String key) { @@ -66,21 +67,24 @@ public class ReduceBrightColorsIntensityPreferenceController extends SliderPrefe @Override public int getSliderPosition() { - return mColorDisplayManager.getReduceBrightColorsStrength(); + return INVERSE_PERCENTAGE_BASE - mColorDisplayManager.getReduceBrightColorsStrength(); } @Override public boolean setSliderPosition(int position) { - return mColorDisplayManager.setReduceBrightColorsStrength(position); + return mColorDisplayManager.setReduceBrightColorsStrength( + INVERSE_PERCENTAGE_BASE - position); } @Override public int getMax() { - return ColorDisplayManager.getMaximumReduceBrightColorsStrength(mContext); + return INVERSE_PERCENTAGE_BASE + - ColorDisplayManager.getMinimumReduceBrightColorsStrength(mContext); } @Override public int getMin() { - return ColorDisplayManager.getMinimumReduceBrightColorsStrength(mContext); + return INVERSE_PERCENTAGE_BASE + - ColorDisplayManager.getMaximumReduceBrightColorsStrength(mContext); } } diff --git a/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java index 9d948583fd..934907181a 100644 --- a/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java @@ -396,10 +396,12 @@ public class ToggleAccessibilityServicePreferenceFragment extends // Settings animated image. final int animatedImageRes = arguments.getInt( AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES); - mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) - .authority(mComponentName.getPackageName()) - .appendPath(String.valueOf(animatedImageRes)) - .build(); + if (animatedImageRes > 0) { + mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) + .authority(mComponentName.getPackageName()) + .appendPath(String.valueOf(animatedImageRes)) + .build(); + } // Get Accessibility service name. mPackageName = getAccessibilityServiceInfo().getResolveInfo().loadLabel( diff --git a/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java index 7fd30c6755..b41bafda51 100644 --- a/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java @@ -80,7 +80,7 @@ public class ToggleColorInversionPreferenceFragment extends mHtmlDescription = getText(R.string.accessibility_display_inversion_preference_subtitle); mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) .authority(getPrefContext().getPackageName()) - .appendPath(String.valueOf(R.drawable.accessibility_color_inversion_banner)) + .appendPath(String.valueOf(R.raw.accessibility_color_inversion_banner)) .build(); final List<String> enableServiceFeatureKeys = new ArrayList<>(/* initialCapacity= */ 1); enableServiceFeatureKeys.add(ENABLED); diff --git a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java index 1002a8491c..640ae53216 100644 --- a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java @@ -90,7 +90,7 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference private static final String DRAWABLE_FOLDER = "drawable"; protected static final String KEY_USE_SERVICE_PREFERENCE = "use_service"; - protected static final String KEY_GENERAL_CATEGORY = "general_categories"; + public static final String KEY_GENERAL_CATEGORY = "general_categories"; protected static final String KEY_HTML_DESCRIPTION_PREFERENCE = "html_description"; private static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference"; protected static final String KEY_SAVED_USER_SHORTCUT_TYPE = "shortcut_type"; diff --git a/src/com/android/settings/accessibility/ToggleReduceBrightColorsPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleReduceBrightColorsPreferenceFragment.java index b629eaa102..e60751e56b 100644 --- a/src/com/android/settings/accessibility/ToggleReduceBrightColorsPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleReduceBrightColorsPreferenceFragment.java @@ -62,7 +62,7 @@ public class ToggleReduceBrightColorsPreferenceFragment extends ToggleFeaturePre mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) .authority(getPrefContext().getPackageName()) - .appendPath(String.valueOf(R.drawable.extra_dim_banner)) + .appendPath(String.valueOf(R.raw.extra_dim_banner)) .build(); mComponentName = AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME; mPackageName = getText(R.string.reduce_bright_colors_preference_title); diff --git a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java index ca33a96472..9266f720ca 100644 --- a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java @@ -89,7 +89,7 @@ public class ToggleScreenMagnificationPreferenceFragment extends mPackageName = getString(R.string.accessibility_screen_magnification_title); mImageUri = new Uri.Builder().scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) .authority(getPrefContext().getPackageName()) - .appendPath(String.valueOf(R.drawable.accessibility_magnification_banner)) + .appendPath(String.valueOf(R.raw.accessibility_magnification_banner)) .build(); mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> { removeDialog(DialogEnums.EDIT_SHORTCUT); diff --git a/src/com/android/settings/accounts/AvatarViewMixin.java b/src/com/android/settings/accounts/AvatarViewMixin.java index c4ab55a46b..7a2565c45b 100644 --- a/src/com/android/settings/accounts/AvatarViewMixin.java +++ b/src/com/android/settings/accounts/AvatarViewMixin.java @@ -39,7 +39,6 @@ import androidx.lifecycle.OnLifecycleEvent; import com.android.settings.R; import com.android.settings.homepage.SettingsHomepageActivity; import com.android.settings.overlay.FeatureFactory; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.utils.ThreadUtils; import java.net.URISyntaxException; @@ -59,6 +58,7 @@ public class AvatarViewMixin implements LifecycleObserver { private static final String METHOD_GET_ACCOUNT_AVATAR = "getAccountAvatar"; private static final String KEY_AVATAR_BITMAP = "account_avatar"; private static final String KEY_ACCOUNT_NAME = "account_name"; + private static final String KEY_AVATAR_ICON = "avatar_icon"; private static final String EXTRA_ACCOUNT_NAME = "extra.accountName"; private final Context mContext; @@ -105,11 +105,8 @@ public class AvatarViewMixin implements LifecycleObserver { return; } - final MetricsFeatureProvider metricsFeatureProvider = FeatureFactory.getFactory( - mContext).getMetricsFeatureProvider(); - metricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN, - SettingsEnums.CLICK_ACCOUNT_AVATAR, SettingsEnums.SETTINGS_HOMEPAGE, - null /* key */, Integer.MIN_VALUE /* value */); + FeatureFactory.getFactory(mContext).getMetricsFeatureProvider() + .logSettingsTileClick(KEY_AVATAR_ICON, SettingsEnums.SETTINGS_HOMEPAGE); // Here may have two different UI while start the activity. // It will display adding account UI when device has no any account. diff --git a/src/com/android/settings/applications/AppStateAlarmsAndRemindersBridge.java b/src/com/android/settings/applications/AppStateAlarmsAndRemindersBridge.java index 4e9a96efb5..cf938a5e3d 100644 --- a/src/com/android/settings/applications/AppStateAlarmsAndRemindersBridge.java +++ b/src/com/android/settings/applications/AppStateAlarmsAndRemindersBridge.java @@ -19,6 +19,7 @@ package com.android.settings.applications; import android.Manifest; import android.app.AlarmManager; import android.app.AppGlobals; +import android.app.compat.CompatChanges; import android.content.Context; import android.content.pm.IPackageManager; import android.os.RemoteException; @@ -63,14 +64,21 @@ public class AppStateAlarmsAndRemindersBridge extends AppStateBaseBridge { } } + private boolean isChangeEnabled(String packageName, int userId) { + return CompatChanges.isChangeEnabled(AlarmManager.REQUIRE_EXACT_ALARM_PERMISSION, + packageName, UserHandle.of(userId)); + } + /** * Returns information regarding {@link Manifest.permission#SCHEDULE_EXACT_ALARM} for the given * package and uid. */ public AlarmsAndRemindersState createPermissionState(String packageName, int uid) { - final boolean permissionRequested = ArrayUtils.contains(mRequesterPackages, packageName); - final boolean permissionGranted = mAlarmManager.hasScheduleExactAlarm(packageName, - UserHandle.getUserId(uid)); + final int userId = UserHandle.getUserId(uid); + + final boolean permissionRequested = ArrayUtils.contains(mRequesterPackages, packageName) + && isChangeEnabled(packageName, userId); + final boolean permissionGranted = mAlarmManager.hasScheduleExactAlarm(packageName, userId); return new AlarmsAndRemindersState(permissionRequested, permissionGranted); } diff --git a/src/com/android/settings/applications/HibernatedAppsPreferenceController.java b/src/com/android/settings/applications/HibernatedAppsPreferenceController.java index bf12b86c00..898d7096ee 100644 --- a/src/com/android/settings/applications/HibernatedAppsPreferenceController.java +++ b/src/com/android/settings/applications/HibernatedAppsPreferenceController.java @@ -61,6 +61,7 @@ public final class HibernatedAppsPreferenceController extends BasePreferenceCont private PreferenceScreen mScreen; private int mUnusedCount = 0; private boolean mLoadingUnusedApps; + private boolean mLoadedUnusedCount; private final Executor mBackgroundExecutor; private final Executor mMainExecutor; @@ -79,14 +80,15 @@ public final class HibernatedAppsPreferenceController extends BasePreferenceCont @Override public int getAvailabilityStatus() { - return isHibernationEnabled() && mUnusedCount > 0 - ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + return isHibernationEnabled() ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; } @Override public CharSequence getSummary() { - return mContext.getResources().getQuantityString( - R.plurals.unused_apps_summary, mUnusedCount, mUnusedCount); + return mLoadedUnusedCount + ? mContext.getResources().getQuantityString( + R.plurals.unused_apps_summary, mUnusedCount, mUnusedCount) + : mContext.getResources().getString(R.string.summary_placeholder); } @Override @@ -111,8 +113,8 @@ public final class HibernatedAppsPreferenceController extends BasePreferenceCont loadUnusedCount(unusedCount -> { mUnusedCount = unusedCount; mLoadingUnusedApps = false; + mLoadedUnusedCount = true; mMainExecutor.execute(() -> { - super.displayPreference(mScreen); Preference pref = mScreen.findPreference(mPreferenceKey); refreshSummary(pref); }); diff --git a/src/com/android/settings/applications/RunningServices.java b/src/com/android/settings/applications/RunningServices.java index 4d13241126..b1689d5c59 100644 --- a/src/com/android/settings/applications/RunningServices.java +++ b/src/com/android/settings/applications/RunningServices.java @@ -72,7 +72,11 @@ public class RunningServices extends SettingsPreferenceFragment { public void onResume() { super.onResume(); boolean haveData = mRunningProcessesView.doResume(this, mRunningProcessesAvail); - mLoadingViewController.handleLoadingContainer(haveData /* done */, false /* animate */); + if (haveData) { + mLoadingViewController.showContent(false /* animate */); + } else { + mLoadingViewController.showLoadingView(); + } } @Override diff --git a/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetailPreferenceController.java b/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetailPreferenceController.java new file mode 100644 index 0000000000..cfd4bf1c26 --- /dev/null +++ b/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetailPreferenceController.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 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.applications.appinfo; + +import android.content.Context; +import android.content.pm.PackageInfo; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; + +import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.applications.AppStateAlarmsAndRemindersBridge; + +/** + * Preference controller for + * {@link com.android.settings.applications.appinfo.AlarmsAndRemindersDetails} Settings fragment. + */ +public class AlarmsAndRemindersDetailPreferenceController extends AppInfoPreferenceControllerBase { + + private String mPackageName; + + public AlarmsAndRemindersDetailPreferenceController(Context context, String key) { + super(context, key); + } + + @Override + public int getAvailabilityStatus() { + return isCandidate() ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + } + + @Override + public void updateState(Preference preference) { + preference.setSummary(getPreferenceSummary()); + } + + @Override + protected Class<? extends SettingsPreferenceFragment> getDetailFragmentClass() { + return AlarmsAndRemindersDetails.class; + } + + @VisibleForTesting + CharSequence getPreferenceSummary() { + return AlarmsAndRemindersDetails.getSummary(mContext, mParent.getAppEntry()); + } + + @VisibleForTesting + boolean isCandidate() { + final PackageInfo packageInfo = mParent.getPackageInfo(); + if (packageInfo == null) { + return false; + } + final AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState appState = + new AppStateAlarmsAndRemindersBridge(mContext, null, null).createPermissionState( + mPackageName, packageInfo.applicationInfo.uid); + return appState.shouldBeVisible(); + } + + void setPackageName(String packageName) { + mPackageName = packageName; + } +} diff --git a/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetails.java b/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetails.java index 3765dd9b68..648696ba25 100644 --- a/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetails.java +++ b/src/com/android/settings/applications/appinfo/AlarmsAndRemindersDetails.java @@ -18,7 +18,6 @@ package com.android.settings.applications.appinfo; import static android.app.Activity.RESULT_CANCELED; import static android.app.Activity.RESULT_OK; -import android.app.ActivityManager; import android.app.AppOpsManager; import android.app.settings.SettingsEnums; import android.content.Context; @@ -49,25 +48,20 @@ public class AlarmsAndRemindersDetails extends AppInfoWithHeader private AppOpsManager mAppOpsManager; private RestrictedSwitchPreference mSwitchPref; private AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState mPermissionState; - private ActivityManager mActivityManager; private volatile Boolean mUncommittedState; /** * Returns the string that states whether the app has access to * {@link android.Manifest.permission#SCHEDULE_EXACT_ALARM}. */ - public static int getSummary(Context context, AppEntry entry) { - final AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState state; - if (entry.extraInfo instanceof AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState) { - state = (AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState) entry.extraInfo; - } else { - state = new AppStateAlarmsAndRemindersBridge(context, /*appState=*/null, - /*callback=*/null).createPermissionState(entry.info.packageName, - entry.info.uid); - } - - return state.isAllowed() ? R.string.app_permission_summary_allowed - : R.string.app_permission_summary_not_allowed; + public static CharSequence getSummary(Context context, AppEntry entry) { + final AppStateAlarmsAndRemindersBridge.AlarmsAndRemindersState state = + new AppStateAlarmsAndRemindersBridge(context, /*appState=*/null, + /*callback=*/null).createPermissionState(entry.info.packageName, + entry.info.uid); + + return context.getString(state.isAllowed() ? R.string.app_permission_summary_allowed + : R.string.app_permission_summary_not_allowed); } @Override @@ -77,7 +71,6 @@ public class AlarmsAndRemindersDetails extends AppInfoWithHeader final Context context = getActivity(); mAppBridge = new AppStateAlarmsAndRemindersBridge(context, mState, /*callback=*/null); mAppOpsManager = context.getSystemService(AppOpsManager.class); - mActivityManager = context.getSystemService(ActivityManager.class); if (savedInstanceState != null) { mUncommittedState = (Boolean) savedInstanceState.get(UNCOMMITTED_STATE_KEY); @@ -115,10 +108,6 @@ public class AlarmsAndRemindersDetails extends AppInfoWithHeader final int uid = mPackageInfo.applicationInfo.uid; mAppOpsManager.setUidMode(AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM, uid, newState ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_ERRORED); - if (!newState) { - mActivityManager.killUid(uid, - AppOpsManager.OPSTR_SCHEDULE_EXACT_ALARM + " no longer allowed."); - } } private void logPermissionChange(boolean newState, String packageName) { diff --git a/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java b/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java index 1f0777a5ff..d83f5d147d 100644 --- a/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java +++ b/src/com/android/settings/applications/appinfo/AppBatteryPreferenceController.java @@ -18,11 +18,14 @@ package com.android.settings.applications.appinfo; import android.content.Context; import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.os.AsyncTask; import android.os.BatteryUsageStats; import android.os.Bundle; import android.os.UidBatteryConsumer; +import android.os.UserHandle; import android.os.UserManager; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; @@ -40,6 +43,8 @@ import com.android.settings.fuelgauge.BatteryDiffEntry; import com.android.settings.fuelgauge.BatteryEntry; import com.android.settings.fuelgauge.BatteryUsageStatsLoader; import com.android.settings.fuelgauge.BatteryUtils; +import com.android.settings.fuelgauge.PowerUsageFeatureProvider; +import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnPause; @@ -50,6 +55,7 @@ import java.util.List; public class AppBatteryPreferenceController extends BasePreferenceController implements LifecycleObserver, OnResume, OnPause { + private static final String TAG = "AppBatteryPreferenceController"; private static final String KEY_BATTERY = "battery"; @VisibleForTesting @@ -61,13 +67,16 @@ public class AppBatteryPreferenceController extends BasePreferenceController BatteryUsageStats mBatteryUsageStats; @VisibleForTesting UidBatteryConsumer mUidBatteryConsumer; + @VisibleForTesting + BatteryDiffEntry mBatteryDiffEntry; + @VisibleForTesting + boolean mIsChartGraphEnabled; private Preference mPreference; private final AppInfoDashboardFragment mParent; private String mBatteryPercent; private final String mPackageName; private final int mUid; - private BatteryDiffEntry mBatteryDiffEntry; private boolean mBatteryUsageStatsLoaded = false; private boolean mBatteryDiffEntriesLoaded = false; @@ -78,6 +87,7 @@ public class AppBatteryPreferenceController extends BasePreferenceController mBatteryUtils = BatteryUtils.getInstance(mContext); mPackageName = packageName; mUid = uid; + refreshFeatureFlag(mContext); if (lifecycle != null) { lifecycle.addObserver(this); } @@ -108,7 +118,8 @@ public class AppBatteryPreferenceController extends BasePreferenceController mParent.getActivity(), mParent, mBatteryDiffEntry, - mBatteryPercent, + Utils.formatPercentage( + mBatteryDiffEntry.getPercentOfTotal(), /* round */ true), /*isValidToShowSummary=*/ true, /*slotInformation=*/ null); return true; @@ -120,8 +131,9 @@ public class AppBatteryPreferenceController extends BasePreferenceController final BatteryEntry entry = new BatteryEntry(mContext, /* handler */null, userManager, mUidBatteryConsumer, /* isHidden */ false, mUidBatteryConsumer.getUid(), /* packages */ null, mPackageName); - AdvancedPowerUsageDetail.startBatteryDetailPage(mParent.getActivity(), mParent, - entry, mBatteryPercent); + AdvancedPowerUsageDetail.startBatteryDetailPage(mParent.getActivity(), mParent, entry, + mIsChartGraphEnabled ? Utils.formatPercentage(0) : mBatteryPercent, + !mIsChartGraphEnabled); } else { AdvancedPowerUsageDetail.startBatteryDetailPage(mParent.getActivity(), mParent, mPackageName); @@ -161,12 +173,29 @@ public class AppBatteryPreferenceController extends BasePreferenceController @Override protected void onPostExecute(BatteryDiffEntry batteryDiffEntry) { mBatteryDiffEntry = batteryDiffEntry; - mBatteryDiffEntriesLoaded = true; - mPreference.setEnabled(mBatteryUsageStatsLoaded); + updateBatteryWithDiffEntry(); } }.execute(); } + @VisibleForTesting + void updateBatteryWithDiffEntry() { + if (mIsChartGraphEnabled) { + if (mBatteryDiffEntry != null && mBatteryDiffEntry.mConsumePower > 0) { + mBatteryPercent = Utils.formatPercentage( + mBatteryDiffEntry.getPercentOfTotal(), /* round */ true); + mPreference.setSummary(mContext.getString( + R.string.battery_summary_24hr, mBatteryPercent)); + } else { + mPreference.setSummary( + mContext.getString(R.string.no_battery_summary_24hr)); + } + } + + mBatteryDiffEntriesLoaded = true; + mPreference.setEnabled(mBatteryUsageStatsLoaded); + } + private void onLoadFinished() { if (mBatteryUsageStats == null) { return; @@ -182,10 +211,33 @@ public class AppBatteryPreferenceController extends BasePreferenceController } } + private void refreshFeatureFlag(Context context) { + if (isWorkProfile(context)) { + try { + context = context.createPackageContextAsUser( + context.getPackageName(), 0, UserHandle.OWNER); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "context.createPackageContextAsUser() fail: " + e); + } + } + + final PowerUsageFeatureProvider powerUsageFeatureProvider = + FeatureFactory.getFactory(context).getPowerUsageFeatureProvider(context); + mIsChartGraphEnabled = powerUsageFeatureProvider.isChartGraphEnabled(context); + } + + private boolean isWorkProfile(Context context) { + final UserManager userManager = context.getSystemService(UserManager.class); + return userManager.isManagedProfile() && !userManager.isSystemUser(); + } + @VisibleForTesting void updateBattery() { mBatteryUsageStatsLoaded = true; mPreference.setEnabled(mBatteryDiffEntriesLoaded); + if (mIsChartGraphEnabled) { + return; + } if (isBatteryStatsAvailable()) { final int percentOfMax = (int) mBatteryUtils.calculateBatteryPercent( mUidBatteryConsumer.getConsumedPower(), mBatteryUsageStats.getConsumedPower(), diff --git a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java index cb0ed07b4a..6d3aaa3c2c 100755 --- a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java +++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java @@ -197,8 +197,14 @@ public class AppInfoDashboardFragment extends DashboardFragment acrossProfiles.setPackageName(packageName); acrossProfiles.setParentFragment(this); + final AlarmsAndRemindersDetailPreferenceController alarmsAndReminders = + use(AlarmsAndRemindersDetailPreferenceController.class); + alarmsAndReminders.setPackageName(packageName); + alarmsAndReminders.setParentFragment(this); + use(AdvancedAppInfoPreferenceCategoryController.class).setChildren(Arrays.asList( - writeSystemSettings, drawOverlay, pip, externalSource, acrossProfiles)); + writeSystemSettings, drawOverlay, pip, externalSource, acrossProfiles, + alarmsAndReminders)); } @Override @@ -303,11 +309,6 @@ public class AppInfoDashboardFragment extends DashboardFragment return controllers; } - @Override - protected boolean isParalleledControllers() { - return true; - } - void addToCallbackList(Callback callback) { if (callback != null) { mCallbacks.add(callback); diff --git a/src/com/android/settings/applications/intentpicker/AppLaunchSettings.java b/src/com/android/settings/applications/intentpicker/AppLaunchSettings.java index 439a6a31b4..43c377aff7 100644 --- a/src/com/android/settings/applications/intentpicker/AppLaunchSettings.java +++ b/src/com/android/settings/applications/intentpicker/AppLaunchSettings.java @@ -46,7 +46,6 @@ import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.applications.AppInfoBase; import com.android.settings.applications.ClearDefaultsPreference; -import com.android.settings.utils.AnnotationSpan; import com.android.settings.widget.EntityHeaderController; import com.android.settingslib.applications.AppUtils; import com.android.settingslib.widget.FooterPreference; @@ -73,8 +72,6 @@ public class AppLaunchSettings extends AppInfoBase implements "open_by_default_selected_links_category"; private static final String OTHER_DETAILS_PREF_CATEGORY_KEY = "app_launch_other_defaults"; - // Url and Uri - private static final String ANNOTATION_URL = "url"; private static final String LEARN_MORE_URI = "https://developer.android.com/training/app-links/verify-site-associations"; @@ -356,16 +353,22 @@ public class AppLaunchSettings extends AppInfoBase implements } private void initFooter() { - // learn more - final AnnotationSpan.LinkInfo linkInfo = - new AnnotationSpan.LinkInfo(ANNOTATION_URL, v -> { - final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(LEARN_MORE_URI)); - mContext.startActivity(intent); - }); final CharSequence footerText = mContext.getText(R.string.app_launch_footer); final FooterPreference footerPreference = (FooterPreference) findPreference( FOOTER_PREF_KEY); - footerPreference.setTitle(AnnotationSpan.linkify(footerText, linkInfo)); + footerPreference.setTitle(footerText); + // learn more + footerPreference.setLearnMoreAction(view -> { + final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(LEARN_MORE_URI)); + mContext.startActivity(intent); + }); + final String learnMoreContentDescription = mContext.getString( + R.string.footer_learn_more_content_description, getLabelName()); + footerPreference.setLearnMoreContentDescription(learnMoreContentDescription); + } + + private String getLabelName() { + return mContext.getString(R.string.launch_by_default); } private boolean isClearDefaultsEnabled() { diff --git a/src/com/android/settings/applications/manageapplications/ManageApplications.java b/src/com/android/settings/applications/manageapplications/ManageApplications.java index 7509a78891..43e929b805 100644 --- a/src/com/android/settings/applications/manageapplications/ManageApplications.java +++ b/src/com/android/settings/applications/manageapplications/ManageApplications.java @@ -73,6 +73,8 @@ import android.widget.Spinner; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.annotation.WorkerThread; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.view.ViewCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -130,6 +132,8 @@ import com.android.settingslib.fuelgauge.PowerAllowlistBackend; import com.android.settingslib.utils.ThreadUtils; import com.android.settingslib.widget.settingsspinner.SettingsSpinnerAdapter; +import com.google.android.material.appbar.AppBarLayout; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -143,7 +147,8 @@ import java.util.Set; * intent. */ public class ManageApplications extends InstrumentedFragment - implements View.OnClickListener, OnItemSelectedListener, SearchView.OnQueryTextListener { + implements View.OnClickListener, OnItemSelectedListener, SearchView.OnQueryTextListener, + MenuItem.OnActionExpandListener { static final String TAG = "ManageApplications"; static final boolean DEBUG = Build.IS_DEBUGGABLE; @@ -203,7 +208,6 @@ public class ManageApplications extends InstrumentedFragment private ApplicationsAdapter mApplications; private View mLoadingContainer; - private View mListContainer; private SearchView mSearchView; // Size resource used for packages whose size computation failed for some reason @@ -256,6 +260,7 @@ public class ManageApplications extends InstrumentedFragment private boolean mIsPersonalOnly; private View mEmptyView; private int mFilterType; + private AppBarLayout mAppBarLayout; @Override public void onCreate(Bundle savedInstanceState) { @@ -396,25 +401,21 @@ public class ManageApplications extends InstrumentedFragment mRootView = inflater.inflate(R.layout.manage_applications_apps, null); mLoadingContainer = mRootView.findViewById(R.id.loading_container); - mListContainer = mRootView.findViewById(R.id.list_container); - if (mListContainer != null) { - // Create adapter and list view here - mEmptyView = mListContainer.findViewById(android.R.id.empty); + mEmptyView = mRootView.findViewById(android.R.id.empty); + mRecyclerView = mRootView.findViewById(R.id.apps_list); - mApplications = new ApplicationsAdapter(mApplicationsState, this, mFilter, - savedInstanceState); - if (savedInstanceState != null) { - mApplications.mHasReceivedLoadEntries = - savedInstanceState.getBoolean(EXTRA_HAS_ENTRIES, false); - mApplications.mHasReceivedBridgeCallback = - savedInstanceState.getBoolean(EXTRA_HAS_BRIDGE, false); - } - mRecyclerView = mListContainer.findViewById(R.id.apps_list); - mRecyclerView.setItemAnimator(null); - mRecyclerView.setLayoutManager(new LinearLayoutManager( - getContext(), RecyclerView.VERTICAL, false /* reverseLayout */)); - mRecyclerView.setAdapter(mApplications); + mApplications = new ApplicationsAdapter(mApplicationsState, this, mFilter, + savedInstanceState); + if (savedInstanceState != null) { + mApplications.mHasReceivedLoadEntries = + savedInstanceState.getBoolean(EXTRA_HAS_ENTRIES, false); + mApplications.mHasReceivedBridgeCallback = + savedInstanceState.getBoolean(EXTRA_HAS_BRIDGE, false); } + mRecyclerView.setItemAnimator(null); + mRecyclerView.setLayoutManager(new LinearLayoutManager( + getContext(), RecyclerView.VERTICAL, false /* reverseLayout */)); + mRecyclerView.setAdapter(mApplications); // We have to do this now because PreferenceFrameLayout looks at it // only when the view is added. @@ -426,6 +427,9 @@ public class ManageApplications extends InstrumentedFragment mResetAppsHelper.onRestoreInstanceState(savedInstanceState); + mAppBarLayout = getActivity().findViewById(R.id.app_bar); + disableToolBarScrollableBehavior(); + return mRootView; } @@ -659,6 +663,7 @@ public class ManageApplications extends InstrumentedFragment final MenuItem searchMenuItem = menu.findItem(R.id.search_app_list_menu); if (searchMenuItem != null) { + searchMenuItem.setOnActionExpandListener(this); mSearchView = (SearchView) searchMenuItem.getActionView(); mSearchView.setQueryHint(getText(R.string.search_settings)); mSearchView.setOnQueryTextListener(this); @@ -671,6 +676,23 @@ public class ManageApplications extends InstrumentedFragment } @Override + public boolean onMenuItemActionExpand(MenuItem item) { + // To prevent a large space on tool bar. + mAppBarLayout.setExpanded(false /*expanded*/, false /*animate*/); + // To prevent user can expand the collapsing tool bar view. + ViewCompat.setNestedScrollingEnabled(mRecyclerView, false); + return true; + } + + @Override + public boolean onMenuItemActionCollapse(MenuItem item) { + // We keep the collapsed status after user cancel the search function. + mAppBarLayout.setExpanded(false /*expanded*/, false /*animate*/); + ViewCompat.setNestedScrollingEnabled(mRecyclerView, true); + return true; + } + + @Override public void onPrepareOptionsMenu(Menu menu) { updateOptionsMenu(); } @@ -791,6 +813,9 @@ public class ManageApplications extends InstrumentedFragment mCurrentPkgName = entry.info.packageName; mCurrentUid = entry.info.uid; startApplicationDetailsActivity(); + // We disable the scrolling ability in onMenuItemActionCollapse, we should recover it + // if user selects any app item. + ViewCompat.setNestedScrollingEnabled(mRecyclerView, true); } } @@ -842,6 +867,20 @@ public class ManageApplications extends InstrumentedFragment } } + private void disableToolBarScrollableBehavior() { + final CoordinatorLayout.LayoutParams params = + (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams(); + final AppBarLayout.Behavior behavior = new AppBarLayout.Behavior(); + behavior.setDragCallback( + new AppBarLayout.Behavior.DragCallback() { + @Override + public boolean canDrag(@NonNull AppBarLayout appBarLayout) { + return false; + } + }); + params.setBehavior(behavior); + } + static class FilterSpinnerAdapter extends SettingsSpinnerAdapter<CharSequence> { private final ManageApplications mManageApplications; @@ -941,16 +980,8 @@ public class ManageApplications extends InstrumentedFragment // overlapped by floating filter. if (hasFilter) { mManageApplications.mSpinnerHeader.setVisibility(View.VISIBLE); - mManageApplications.mRecyclerView.setPadding(0 /* left */, - mContext.getResources().getDimensionPixelSize( - R.dimen.app_bar_height) /* top */, - 0 /* right */, - 0 /* bottom */); } else { mManageApplications.mSpinnerHeader.setVisibility(View.GONE); - mManageApplications.mRecyclerView.setPadding(0 /* left */, 0 /* top */, - 0 /* right */, - 0 /* bottom */); } } } @@ -1000,7 +1031,8 @@ public class ManageApplications extends InstrumentedFragment mManageApplications = manageApplications; mLoadingViewController = new LoadingViewController( mManageApplications.mLoadingContainer, - mManageApplications.mListContainer + mManageApplications.mRecyclerView, + mManageApplications.mEmptyView ); mContext = manageApplications.getActivity(); mIconDrawableFactory = IconDrawableFactory.newInstance(mContext); @@ -1259,11 +1291,9 @@ public class ManageApplications extends InstrumentedFragment mOriginalEntries = entries; notifyDataSetChanged(); if (getItemCount() == 0) { - mManageApplications.mRecyclerView.setVisibility(View.GONE); - mManageApplications.mEmptyView.setVisibility(View.VISIBLE); + mLoadingViewController.showEmpty(false /* animate */); } else { - mManageApplications.mEmptyView.setVisibility(View.GONE); - mManageApplications.mRecyclerView.setVisibility(View.VISIBLE); + mLoadingViewController.showContent(false /* animate */); if (mManageApplications.mSearchView != null && mManageApplications.mSearchView.isVisibleToUser()) { @@ -1280,10 +1310,6 @@ public class ManageApplications extends InstrumentedFragment mLastIndex = -1; } - if (mSession.getAllApps().size() != 0 - && mManageApplications.mListContainer.getVisibility() != View.VISIBLE) { - mLoadingViewController.showContent(true /* animate */); - } if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) { // No enabled or disabled filters for usage access. return; diff --git a/src/com/android/settings/biometrics/BiometricEnrollActivity.java b/src/com/android/settings/biometrics/BiometricEnrollActivity.java index 11313fd52d..db5e003456 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollActivity.java +++ b/src/com/android/settings/biometrics/BiometricEnrollActivity.java @@ -19,6 +19,9 @@ package com.android.settings.biometrics; import static android.provider.Settings.ACTION_BIOMETRIC_ENROLL; import static android.provider.Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED; +import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_DENIED; +import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_GRANTED; + import android.annotation.NonNull; import android.app.admin.DevicePolicyManager; import android.app.settings.SettingsEnums; @@ -64,6 +67,10 @@ public class BiometricEnrollActivity extends InstrumentedActivity { private static final int REQUEST_CHOOSE_LOCK = 1; private static final int REQUEST_CONFIRM_LOCK = 2; + // prompt for parental consent options + private static final int REQUEST_CHOOSE_OPTIONS = 3; + // prompt hand phone back to parent after enrollment + private static final int REQUEST_HANDOFF_PARENT = 4; public static final int RESULT_SKIP = BiometricEnrollBase.RESULT_SKIP; @@ -71,8 +78,12 @@ public class BiometricEnrollActivity extends InstrumentedActivity { // this only applies to fingerprint. public static final String EXTRA_SKIP_INTRO = "skip_intro"; + // TODO: temporary while waiting for team to add real flag + public static final String EXTRA_TEMP_REQUIRE_PARENTAL_CONSENT = "require_consent"; + private static final String SAVED_STATE_CONFIRMING_CREDENTIALS = "confirming_credentials"; private static final String SAVED_STATE_ENROLL_ACTION_LOGGED = "enroll_action_logged"; + private static final String SAVED_STATE_PARENTAL_OPTIONS = "enroll_preferences"; private static final String SAVED_STATE_GK_PW_HANDLE = "gk_pw_handle"; public static final class InternalActivity extends BiometricEnrollActivity {} @@ -80,9 +91,14 @@ public class BiometricEnrollActivity extends InstrumentedActivity { private int mUserId = UserHandle.myUserId(); private boolean mConfirmingCredentials; private boolean mIsEnrollActionLogged; - private boolean mIsFaceEnrollable; - private boolean mIsFingerprintEnrollable; + private boolean mHasFeatureFace = false; + private boolean mHasFeatureFingerprint = false; + private boolean mIsFaceEnrollable = false; + private boolean mIsFingerprintEnrollable = false; + private boolean mParentalOptionsRequired = false; + private Bundle mParentalOptions; @Nullable private Long mGkPwHandle; + @Nullable private ParentalConsentHelper mParentalConsentHelper; @Nullable private MultiBiometricEnrollHelper mMultiBiometricEnrollHelper; @Override @@ -101,6 +117,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity { SAVED_STATE_CONFIRMING_CREDENTIALS, false); mIsEnrollActionLogged = savedInstanceState.getBoolean( SAVED_STATE_ENROLL_ACTION_LOGGED, false); + mParentalOptions = savedInstanceState.getBundle(SAVED_STATE_PARENTAL_OPTIONS); if (savedInstanceState.containsKey(SAVED_STATE_GK_PW_HANDLE)) { mGkPwHandle = savedInstanceState.getLong(SAVED_STATE_GK_PW_HANDLE); } @@ -141,52 +158,98 @@ public class BiometricEnrollActivity extends InstrumentedActivity { SetupWizardUtils.getThemeString(intent)); } - // Default behavior is to enroll BIOMETRIC_WEAK or above. See ACTION_BIOMETRIC_ENROLL. - final int authenticators = intent.getIntExtra( - EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, Authenticators.BIOMETRIC_WEAK); - - Log.d(TAG, "Authenticators: " + authenticators); - final PackageManager pm = getApplicationContext().getPackageManager(); - final boolean hasFeatureFingerprint = - pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT); - final boolean hasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE); + mHasFeatureFingerprint = pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT); + mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE); + + // determine what can be enrolled final boolean isSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent()); + if (mHasFeatureFace) { + final FaceManager faceManager = getSystemService(FaceManager.class); + final List<FaceSensorPropertiesInternal> faceProperties = + faceManager.getSensorPropertiesInternal(); + if (!faceProperties.isEmpty()) { + final int maxEnrolls = + isSetupWizard ? 1 : faceProperties.get(0).maxEnrollmentsPerUser; + mIsFaceEnrollable = + faceManager.getEnrolledFaces(mUserId).size() < maxEnrolls; + } + } + if (mHasFeatureFingerprint) { + final FingerprintManager fpManager = getSystemService(FingerprintManager.class); + final List<FingerprintSensorPropertiesInternal> fpProperties = + fpManager.getSensorPropertiesInternal(); + if (!fpProperties.isEmpty()) { + final int maxEnrolls = + isSetupWizard ? 1 : fpProperties.get(0).maxEnrollmentsPerUser; + mIsFingerprintEnrollable = + fpManager.getEnrolledFingerprints(mUserId).size() < maxEnrolls; + } + } - if (isSetupWizard) { - if (hasFeatureFace && hasFeatureFingerprint) { - setupForMultiBiometricEnroll(); - } else if (hasFeatureFace) { - launchFaceOnlyEnroll(); - } else if (hasFeatureFingerprint) { - launchFingerprintOnlyEnroll(); - } else { - Log.e(TAG, "No biometrics but started by SUW?"); - finish(); + // TODO(b/188847063): replace with real flag when ready + mParentalOptionsRequired = intent.getBooleanExtra( + BiometricEnrollActivity.EXTRA_TEMP_REQUIRE_PARENTAL_CONSENT, false); + + if (mParentalOptionsRequired && mParentalOptions == null) { + mParentalConsentHelper = new ParentalConsentHelper( + mIsFaceEnrollable, mIsFingerprintEnrollable, mGkPwHandle); + setOrConfirmCredentialsNow(); + } else { + startEnroll(); + } + } + + private void startEnroll() { + // TODO(b/188847063): This can be deleted, but log it now until it's wired up for real. + if (mParentalOptionsRequired) { + if (mParentalOptions == null) { + throw new IllegalStateException("consent options required, but not set"); } + Log.d(TAG, "consent for face: " + + ParentalConsentHelper.hasFaceConsent(mParentalOptions)); + Log.d(TAG, "consent for fingerprint: " + + ParentalConsentHelper.hasFingerprintConsent(mParentalOptions)); } else { - // If the caller is not setup wizard, and the user has something enrolled, finish. + Log.d(TAG, "startEnroll without requiring consent"); + } + + // Default behavior is to enroll BIOMETRIC_WEAK or above. See ACTION_BIOMETRIC_ENROLL. + final int authenticators = getIntent().getIntExtra( + EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, Authenticators.BIOMETRIC_WEAK); + Log.d(TAG, "Authenticators: " + authenticators); + + startEnrollWith(authenticators, WizardManagerHelper.isAnySetupWizard(getIntent())); + } + + private void startEnrollWith(@Authenticators.Types int authenticators, boolean setupWizard) { + // If the caller is not setup wizard, and the user has something enrolled, finish. + if (!setupWizard) { final BiometricManager bm = getSystemService(BiometricManager.class); final @BiometricError int result = bm.canAuthenticate(authenticators); if (result != BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) { - Log.e(TAG, "Unexpected result: " + result); + Log.e(TAG, "Unexpected result (has enrollments): " + result); finish(); return; } + } - // This will need to be updated if the device has sensors other than BIOMETRIC_STRONG - if (authenticators == BiometricManager.Authenticators.DEVICE_CREDENTIAL) { - launchCredentialOnlyEnroll(); - } else if (hasFeatureFace && hasFeatureFingerprint) { - setupForMultiBiometricEnroll(); - } else if (hasFeatureFingerprint) { - launchFingerprintOnlyEnroll(); - } else if (hasFeatureFace) { - launchFaceOnlyEnroll(); + // This will need to be updated if the device has sensors other than BIOMETRIC_STRONG + if (!setupWizard && authenticators == BiometricManager.Authenticators.DEVICE_CREDENTIAL) { + launchCredentialOnlyEnroll(); + } else if (mHasFeatureFace && mHasFeatureFingerprint) { + if (mParentalOptionsRequired && mGkPwHandle != null) { + launchFaceAndFingerprintEnroll(); } else { - Log.e(TAG, "Unknown state, finishing"); - finish(); + setOrConfirmCredentialsNow(); } + } else if (mHasFeatureFingerprint) { + launchFingerprintOnlyEnroll(); + } else if (mHasFeatureFace) { + launchFaceOnlyEnroll(); + } else { + Log.e(TAG, "Unknown state, finishing (was SUW: " + setupWizard + ")"); + finish(); } } @@ -195,6 +258,9 @@ public class BiometricEnrollActivity extends InstrumentedActivity { super.onSaveInstanceState(outState); outState.putBoolean(SAVED_STATE_CONFIRMING_CREDENTIALS, mConfirmingCredentials); outState.putBoolean(SAVED_STATE_ENROLL_ACTION_LOGGED, mIsEnrollActionLogged); + if (mParentalOptions != null) { + outState.putBundle(SAVED_STATE_PARENTAL_OPTIONS, mParentalOptions); + } if (mGkPwHandle != null) { outState.putLong(SAVED_STATE_GK_PW_HANDLE, mGkPwHandle); } @@ -204,31 +270,93 @@ public class BiometricEnrollActivity extends InstrumentedActivity { protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); + // single enrollment is handled entirely by the launched activity + // this handles multi enroll or if parental consent is required + if (mParentalConsentHelper != null) { + handleOnActivityResultWhileConsenting(requestCode, resultCode, data); + } else { + handleOnActivityResultWhileEnrollingMultiple(requestCode, resultCode, data); + } + } + + // handles responses while parental consent is pending + private void handleOnActivityResultWhileConsenting( + int requestCode, int resultCode, Intent data) { + overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out); + + switch (requestCode) { + case REQUEST_CHOOSE_LOCK: + case REQUEST_CONFIRM_LOCK: + mConfirmingCredentials = false; + if (isSuccessfulConfirmOrChooseCredential(requestCode, resultCode)) { + updateGatekeeperPasswordHandle(data); + if (!mParentalConsentHelper.launchNext(this, REQUEST_CHOOSE_OPTIONS)) { + Log.e(TAG, "Nothing to prompt for consent (no modalities enabled)!"); + finish(); + } + } else { + Log.d(TAG, "Unknown result for set/choose lock: " + resultCode); + setResult(resultCode); + finish(); + } + break; + case REQUEST_CHOOSE_OPTIONS: + if (resultCode == RESULT_CONSENT_GRANTED || resultCode == RESULT_CONSENT_DENIED) { + final boolean isStillPrompting = mParentalConsentHelper.launchNext( + this, REQUEST_CHOOSE_OPTIONS, resultCode, data); + if (!isStillPrompting) { + Log.d(TAG, "Enrollment options set, requesting handoff"); + launchHandoffToParent(); + } + } else { + Log.d(TAG, "Unknown or cancelled parental consent"); + setResult(RESULT_CANCELED); + finish(); + } + break; + case REQUEST_HANDOFF_PARENT: + if (resultCode == RESULT_OK) { + Log.d(TAG, "Enrollment options set, starting enrollment"); + mParentalOptions = mParentalConsentHelper.getConsentResult(); + mParentalConsentHelper = null; + startEnroll(); + } else { + Log.d(TAG, "Unknown or cancelled handoff"); + setResult(RESULT_CANCELED); + finish(); + } + break; + default: + Log.w(TAG, "Unknown consenting requestCode: " + requestCode + ", finishing"); + finish(); + } + } + + // handles responses while multi biometric enrollment is pending + private void handleOnActivityResultWhileEnrollingMultiple( + int requestCode, int resultCode, Intent data) { if (mMultiBiometricEnrollHelper == null) { overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out); switch (requestCode) { case REQUEST_CHOOSE_LOCK: - mConfirmingCredentials = false; - if (resultCode == ChooseLockPattern.RESULT_FINISHED) { - startMultiBiometricEnroll(data); - } else { - Log.d(TAG, "Unknown result for chooseLock: " + resultCode); - setResult(resultCode); - finish(); - } - break; case REQUEST_CONFIRM_LOCK: mConfirmingCredentials = false; - if (resultCode == RESULT_OK) { - startMultiBiometricEnroll(data); + final boolean isOk = + isSuccessfulConfirmOrChooseCredential(requestCode, resultCode); + // single modality enrollment requests confirmation directly + // via BiometricEnrollBase#onCreate and should never get here + if (isOk && mHasFeatureFace && mHasFeatureFingerprint) { + updateGatekeeperPasswordHandle(data); + launchFaceAndFingerprintEnroll(); } else { - Log.d(TAG, "Unknown result for confirmLock: " + resultCode); + Log.d(TAG, "Unknown result for set/choose lock: " + resultCode); + setResult(resultCode); finish(); } break; default: - Log.d(TAG, "Unknown requestCode: " + requestCode + ", finishing"); + Log.w(TAG, "Unknown enrolling requestCode: " + requestCode + ", finishing"); finish(); } } else { @@ -236,18 +364,28 @@ public class BiometricEnrollActivity extends InstrumentedActivity { } } + private static boolean isSuccessfulConfirmOrChooseCredential(int requestCode, int resultCode) { + final boolean okChoose = requestCode == REQUEST_CHOOSE_LOCK + && resultCode == ChooseLockPattern.RESULT_FINISHED; + final boolean okConfirm = requestCode == REQUEST_CONFIRM_LOCK + && resultCode == RESULT_OK; + return okChoose || okConfirm; + } + @Override protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) { - final int new_resid = SetupWizardUtils.getTheme(this, getIntent()); + final int newResid = SetupWizardUtils.getTheme(this, getIntent()); theme.applyStyle(R.style.SetupWizardPartnerResource, true); - super.onApplyThemeResource(theme, new_resid, first); + super.onApplyThemeResource(theme, newResid, first); } @Override protected void onStop() { super.onStop(); - if (mConfirmingCredentials || mMultiBiometricEnrollHelper != null) { + if (mConfirmingCredentials + || mMultiBiometricEnrollHelper != null + || mParentalConsentHelper != null) { return; } @@ -257,22 +395,8 @@ public class BiometricEnrollActivity extends InstrumentedActivity { } } - private void setupForMultiBiometricEnroll() { - final FingerprintManager fingerprintManager = getSystemService(FingerprintManager.class); - final FaceManager faceManager = getSystemService(FaceManager.class); - final List<FingerprintSensorPropertiesInternal> fpProperties = - fingerprintManager.getSensorPropertiesInternal(); - final List<FaceSensorPropertiesInternal> faceProperties = - faceManager.getSensorPropertiesInternal(); - - // This would need to be updated for devices with multiple sensors of the same modality - mIsFaceEnrollable = !faceProperties.isEmpty() && - faceManager.getEnrolledFaces(mUserId).size() - < faceProperties.get(0).maxEnrollmentsPerUser; - mIsFingerprintEnrollable = !fpProperties.isEmpty() && - fingerprintManager.getEnrolledFingerprints(mUserId).size() - < fpProperties.get(0).maxEnrollmentsPerUser; + private void setOrConfirmCredentialsNow() { if (!mConfirmingCredentials) { mConfirmingCredentials = true; if (!userHasPassword(mUserId)) { @@ -283,11 +407,11 @@ public class BiometricEnrollActivity extends InstrumentedActivity { } } - private void startMultiBiometricEnroll(Intent data) { + private void updateGatekeeperPasswordHandle(@NonNull Intent data) { mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(data); - mMultiBiometricEnrollHelper = new MultiBiometricEnrollHelper(this, mUserId, - mIsFaceEnrollable, mIsFingerprintEnrollable, mGkPwHandle); - mMultiBiometricEnrollHelper.startNextStep(); + if (mParentalConsentHelper != null) { + mParentalConsentHelper.updateGatekeeperHandle(data); + } } private boolean userHasPassword(int userId) { @@ -299,6 +423,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity { private void launchChooseLock() { Log.d(TAG, "launchChooseLock"); + Intent intent = BiometricUtils.getChooseLockIntent(this, getIntent()); intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true); @@ -312,6 +437,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity { private void launchConfirmLock() { Log.d(TAG, "launchConfirmLock"); + final ChooseLockSettingsHelper.Builder builder = new ChooseLockSettingsHelper.Builder(this); builder.setRequestCode(REQUEST_CONFIRM_LOCK) .setRequestGatekeeperPasswordHandle(true) @@ -335,7 +461,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity { * @param intent Enrollment activity that should be started (e.g. FaceEnrollIntroduction.class, * etc). */ - private void launchEnrollActivity(@NonNull Intent intent) { + private void launchSingleSensorEnrollActivity(@NonNull Intent intent) { intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); byte[] hardwareAuthToken = null; if (this instanceof InternalActivity) { @@ -351,7 +477,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity { // If only device credential was specified, ask the user to only set that up. intent = new Intent(this, ChooseLockGeneric.class); intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true); - launchEnrollActivity(intent); + launchSingleSensorEnrollActivity(intent); } private void launchFingerprintOnlyEnroll() { @@ -363,12 +489,23 @@ public class BiometricEnrollActivity extends InstrumentedActivity { } else { intent = BiometricUtils.getFingerprintIntroIntent(this, getIntent()); } - launchEnrollActivity(intent); + launchSingleSensorEnrollActivity(intent); } private void launchFaceOnlyEnroll() { final Intent intent = BiometricUtils.getFaceIntroIntent(this, getIntent()); - launchEnrollActivity(intent); + launchSingleSensorEnrollActivity(intent); + } + + private void launchFaceAndFingerprintEnroll() { + mMultiBiometricEnrollHelper = new MultiBiometricEnrollHelper(this, mUserId, + mIsFaceEnrollable, mIsFingerprintEnrollable, mGkPwHandle); + mMultiBiometricEnrollHelper.startNextStep(); + } + + private void launchHandoffToParent() { + final Intent intent = BiometricUtils.getHandoffToParentIntent(this, getIntent()); + startActivityForResult(intent, REQUEST_HANDOFF_PARENT); } @Override diff --git a/src/com/android/settings/biometrics/BiometricEnrollBase.java b/src/com/android/settings/biometrics/BiometricEnrollBase.java index b62b35fe20..6e7d04f26b 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollBase.java +++ b/src/com/android/settings/biometrics/BiometricEnrollBase.java @@ -26,6 +26,7 @@ import android.graphics.Color; import android.os.Bundle; import android.os.UserHandle; import android.text.TextUtils; +import android.util.Log; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; @@ -50,12 +51,15 @@ import com.google.android.setupdesign.util.ThemeHelper; */ public abstract class BiometricEnrollBase extends InstrumentedActivity { + private static final String TAG = "BiometricEnrollBase"; + public static final String EXTRA_FROM_SETTINGS_SUMMARY = "from_settings_summary"; public static final String EXTRA_KEY_LAUNCHED_CONFIRM = "launched_confirm_lock"; public static final String EXTRA_KEY_REQUIRE_VISION = "accessibility_vision"; public static final String EXTRA_KEY_REQUIRE_DIVERSITY = "accessibility_diversity"; public static final String EXTRA_KEY_SENSOR_ID = "sensor_id"; public static final String EXTRA_KEY_CHALLENGE = "challenge"; + public static final String EXTRA_KEY_MODALITY = "sensor_modality"; /** * Used by the choose fingerprint wizard to indicate the wizard is @@ -84,11 +88,26 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity { */ public static final int RESULT_TIMEOUT = RESULT_FIRST_USER + 2; + /** + * Used by consent screens to indicate that consent was granted. Extras, such as + * EXTRA_KEY_MODALITY, will be included in the result to provide details about the + * consent that was granted. + */ + public static final int RESULT_CONSENT_GRANTED = RESULT_FIRST_USER + 3; + + /** + * Used by consent screens to indicate that consent was denied. Extras, such as + * EXTRA_KEY_MODALITY, will be included in the result to provide details about the + * consent that was not granted. + */ + public static final int RESULT_CONSENT_DENIED = RESULT_FIRST_USER + 4; + public static final int CHOOSE_LOCK_GENERIC_REQUEST = 1; public static final int BIOMETRIC_FIND_SENSOR_REQUEST = 2; public static final int LEARN_MORE_REQUEST = 3; public static final int CONFIRM_REQUEST = 4; public static final int ENROLL_REQUEST = 5; + /** * Request code when starting another biometric enrollment from within a biometric flow. For * example, when starting fingerprint enroll after face enroll. @@ -242,6 +261,8 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity { } protected void launchConfirmLock(int titleResId) { + Log.d(TAG, "launchConfirmLock"); + final ChooseLockSettingsHelper.Builder builder = new ChooseLockSettingsHelper.Builder(this); builder.setRequestCode(CONFIRM_REQUEST) .setTitle(getString(titleResId)) diff --git a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java index de850672bd..c073c3c2ea 100644 --- a/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/BiometricEnrollIntroduction.java @@ -29,6 +29,7 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.StringRes; import com.android.internal.widget.LockPatternUtils; import com.android.settings.R; @@ -36,11 +37,12 @@ import com.android.settings.SetupWizardUtils; import com.android.settings.password.ChooseLockGeneric; import com.android.settings.password.ChooseLockSettingsHelper; +import com.google.android.setupcompat.template.FooterBarMixin; import com.google.android.setupcompat.template.FooterButton; import com.google.android.setupcompat.util.WizardManagerHelper; +import com.google.android.setupdesign.GlifLayout; import com.google.android.setupdesign.span.LinkSpan; import com.google.android.setupdesign.template.RequireScrollMixin; -import com.google.android.setupdesign.template.RequireScrollMixin.OnRequireScrollStateChangedListener; import com.google.android.setupdesign.util.DynamicColorPalette; /** @@ -183,32 +185,26 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase } } - FooterButton primaryButton = getPrimaryFooterButton(); - FooterButton secondaryButton = getSecondaryFooterButton(); - if (primaryButton == null) { - Log.d(TAG, "getPrimaryFooterButton() was null"); - return; - } - - if (secondaryButton == null) { - Log.d(TAG, "getSecondaryFooterButton() was null"); - return; - } - - // Setup scroll mixin - final RequireScrollMixin requireScrollMixin = getLayout().getMixin( - RequireScrollMixin.class); - requireScrollMixin.requireScrollWithButton(this, primaryButton, getScrollCompletedText(), - this::onNextButtonClick); + final GlifLayout layout = getLayout(); + mFooterBarMixin = layout.getMixin(FooterBarMixin.class); + mFooterBarMixin.setPrimaryButton(getPrimaryFooterButton()); + mFooterBarMixin.setSecondaryButton(getSecondaryFooterButton(), true /* usePrimaryStyle */); + mFooterBarMixin.getSecondaryButton().setVisibility(View.INVISIBLE); - secondaryButton.setVisibility(View.INVISIBLE); + final RequireScrollMixin requireScrollMixin = layout.getMixin(RequireScrollMixin.class); + requireScrollMixin.requireScrollWithButton(this, getPrimaryFooterButton(), + getMoreButtonTextRes(), this::onNextButtonClick); requireScrollMixin.setOnRequireScrollStateChangedListener( - new OnRequireScrollStateChangedListener() { - @Override - public void onRequireScrollStateChanged(boolean scrollNeeded) { - if (!scrollNeeded && secondaryButton.getVisibility() == View.INVISIBLE) { - secondaryButton.setVisibility(View.VISIBLE); - } + scrollNeeded -> { + // Update text of primary button from "More" to "Agree". + final int primaryButtonTextRes = scrollNeeded + ? getMoreButtonTextRes() + : getAgreeButtonTextRes(); + getPrimaryFooterButton().setText(this, primaryButtonTextRes); + + // Show secondary button once scroll is completed. + if (!scrollNeeded) { + getSecondaryFooterButton().setVisibility(View.VISIBLE); } }); } @@ -298,15 +294,19 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase mConfirmingCredentials = false; if (resultCode == RESULT_FINISHED) { updatePasswordQuality(); - overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out); - getNextButton().setEnabled(false); - getChallenge(((sensorId, userId, challenge) -> { - mSensorId = sensorId; - mChallenge = challenge; - mToken = BiometricUtils.requestGatekeeperHat(this, data, mUserId, challenge); - BiometricUtils.removeGatekeeperPasswordHandle(this, data); - getNextButton().setEnabled(true); - })); + final boolean handled = onSetOrConfirmCredentials(data); + if (!handled) { + overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out); + getNextButton().setEnabled(false); + getChallenge(((sensorId, userId, challenge) -> { + mSensorId = sensorId; + mChallenge = challenge; + mToken = BiometricUtils.requestGatekeeperHat(this, data, mUserId, + challenge); + BiometricUtils.removeGatekeeperPasswordHandle(this, data); + getNextButton().setEnabled(true); + })); + } } else { setResult(resultCode, data); finish(); @@ -314,15 +314,19 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase } else if (requestCode == CONFIRM_REQUEST) { mConfirmingCredentials = false; if (resultCode == RESULT_OK && data != null) { - overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out); - getNextButton().setEnabled(false); - getChallenge(((sensorId, userId, challenge) -> { - mSensorId = sensorId; - mChallenge = challenge; - mToken = BiometricUtils.requestGatekeeperHat(this, data, mUserId, challenge); - BiometricUtils.removeGatekeeperPasswordHandle(this, data); - getNextButton().setEnabled(true); - })); + final boolean handled = onSetOrConfirmCredentials(data); + if (!handled) { + overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out); + getNextButton().setEnabled(false); + getChallenge(((sensorId, userId, challenge) -> { + mSensorId = sensorId; + mChallenge = challenge; + mToken = BiometricUtils.requestGatekeeperHat(this, data, mUserId, + challenge); + BiometricUtils.removeGatekeeperPasswordHandle(this, data); + getNextButton().setEnabled(true); + })); + } } else { setResult(resultCode, data); finish(); @@ -339,6 +343,18 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase super.onActivityResult(requestCode, resultCode, data); } + /** + * Called after confirming credentials. Can be used to prevent the default + * behavior of immediately calling #getChallenge (useful to things like intro + * consent screens that don't actually do enrollment and will later start an + * activity that does). + * + * @return True if the default behavior should be skipped and handled by this method instead. + */ + protected boolean onSetOrConfirmCredentials(@Nullable Intent data) { + return false; + } + protected void onCancelButtonClick(View view) { finish(); } @@ -367,17 +383,15 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase return mIconColorFilter; } - @Nullable - protected FooterButton getPrimaryFooterButton() { - return null; - } + @NonNull + protected abstract FooterButton getPrimaryFooterButton(); - @Nullable - protected FooterButton getSecondaryFooterButton() { - return null; - } + @NonNull + protected abstract FooterButton getSecondaryFooterButton(); - protected int getScrollCompletedText() { - return R.string.security_settings_face_enroll_introduction_more; - } + @StringRes + protected abstract int getAgreeButtonTextRes(); + + @StringRes + protected abstract int getMoreButtonTextRes(); } diff --git a/src/com/android/settings/biometrics/BiometricHandoffActivity.java b/src/com/android/settings/biometrics/BiometricHandoffActivity.java new file mode 100644 index 0000000000..7f28ced202 --- /dev/null +++ b/src/com/android/settings/biometrics/BiometricHandoffActivity.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 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.biometrics; + +import android.app.settings.SettingsEnums; +import android.os.Bundle; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.settings.R; + +import com.google.android.setupcompat.template.FooterBarMixin; +import com.google.android.setupcompat.template.FooterButton; +import com.google.android.setupdesign.GlifLayout; + +/** + * Prompts the user to hand the device to their parent or guardian. + */ +public class BiometricHandoffActivity extends BiometricEnrollBase { + + @Nullable + private FooterButton mPrimaryFooterButton; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.biometric_handoff); + + setHeaderText(R.string.biometric_settings_hand_back_to_guardian); + + final GlifLayout layout = getLayout(); + mFooterBarMixin = layout.getMixin(FooterBarMixin.class); + mFooterBarMixin.setPrimaryButton(getPrimaryFooterButton()); + } + + @NonNull + protected FooterButton getPrimaryFooterButton() { + if (mPrimaryFooterButton == null) { + mPrimaryFooterButton = new FooterButton.Builder(this) + .setText(R.string.biometric_settings_hand_back_to_guardian_ok) + .setButtonType(FooterButton.ButtonType.NEXT) + .setListener(this::onNextButtonClick) + .setTheme(R.style.SudGlifButton_Primary) + .build(); + } + return mPrimaryFooterButton; + } + + @Override + protected void onNextButtonClick(View view) { + setResult(RESULT_OK); + finish(); + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.BIOMETRIC_CONSENT_PARENT_TO_CHILD; + } +} diff --git a/src/com/android/settings/biometrics/BiometricUtils.java b/src/com/android/settings/biometrics/BiometricUtils.java index 03cdb0d653..a98c3555f7 100644 --- a/src/com/android/settings/biometrics/BiometricUtils.java +++ b/src/com/android/settings/biometrics/BiometricUtils.java @@ -177,6 +177,19 @@ public class BiometricUtils { } /** + * Start an activity that prompts the user to hand the device to their parent or guardian. + * @param context caller's context + * @param activityIntent The intent that started the caller's activity + * @return Intent for starting BiometricHandoffActivity + */ + public static Intent getHandoffToParentIntent(@NonNull Context context, + @NonNull Intent activityIntent) { + final Intent intent = new Intent(context, BiometricHandoffActivity.class); + WizardManagerHelper.copyWizardManagerExtras(activityIntent, intent); + return intent; + } + + /** * @param activity Reference to the calling activity, used to startActivity * @param intent Intent pointing to the enrollment activity * @param requestCode If non-zero, will invoke startActivityForResult instead of startActivity diff --git a/src/com/android/settings/biometrics/ParentalConsentHelper.java b/src/com/android/settings/biometrics/ParentalConsentHelper.java new file mode 100644 index 0000000000..905a95527e --- /dev/null +++ b/src/com/android/settings/biometrics/ParentalConsentHelper.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2021 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.biometrics; + +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE; + +import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_MODALITY; +import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_DENIED; +import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_GRANTED; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.settings.biometrics.face.FaceEnrollParentalConsent; +import com.android.settings.biometrics.fingerprint.FingerprintEnrollParentalConsent; +import com.android.settings.password.ChooseLockSettingsHelper; + +import com.google.android.setupcompat.util.WizardManagerHelper; + +/** + * Helper for {@link BiometricEnrollActivity} to ask for parental consent prior to actual user + * enrollment. + */ +public class ParentalConsentHelper { + + private static final String KEY_FACE_CONSENT = "face"; + private static final String KEY_FINGERPRINT_CONSENT = "fingerprint"; + + private final boolean mRequireFace; + private final boolean mRequireFingerprint; + + private long mGkPwHandle; + @Nullable + private Boolean mConsentFace; + @Nullable + private Boolean mConsentFingerprint; + + /** + * Helper for aggregating user consent. + * + * @param requireFace if face consent should be shown + * @param requireFingerprint if fingerprint consent should be shown + * @param gkPwHandle for launched intents + */ + public ParentalConsentHelper(boolean requireFace, boolean requireFingerprint, + @Nullable Long gkPwHandle) { + mRequireFace = requireFace; + mRequireFingerprint = requireFingerprint; + mGkPwHandle = gkPwHandle != null ? gkPwHandle : 0L; + } + + /** + * Updated the handle used for launching activities + * + * @param data result intent for credential verification + */ + public void updateGatekeeperHandle(Intent data) { + mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(data); + } + + /** + * Launch the next consent screen. + * + * @param activity root activity + * @param requestCode request code to launch new activity + * @param resultCode result code of the last consent launch + * @param data result data from the last consent launch + * @return true if a consent activity was launched or false when complete + */ + public boolean launchNext(@NonNull Activity activity, int requestCode, int resultCode, + @Nullable Intent data) { + if (data != null) { + switch (data.getIntExtra(EXTRA_KEY_MODALITY, TYPE_NONE)) { + case TYPE_FACE: + mConsentFace = isConsent(resultCode, mConsentFace); + break; + case TYPE_FINGERPRINT: + mConsentFingerprint = isConsent(resultCode, mConsentFingerprint); + break; + } + } + return launchNext(activity, requestCode); + } + + @Nullable + private static Boolean isConsent(int resultCode, @Nullable Boolean defaultValue) { + switch (resultCode) { + case RESULT_CONSENT_GRANTED: + return true; + case RESULT_CONSENT_DENIED: + return false; + } + return defaultValue; + } + + /** @see #launchNext(Activity, int, int, Intent) */ + public boolean launchNext(@NonNull Activity activity, int requestCode) { + final Intent intent = getNextConsentIntent(activity); + if (intent != null) { + WizardManagerHelper.copyWizardManagerExtras(activity.getIntent(), intent); + if (mGkPwHandle != 0) { + intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, mGkPwHandle); + } + activity.startActivityForResult(intent, requestCode); + return true; + } + return false; + } + + @Nullable + private Intent getNextConsentIntent(@NonNull Context context) { + if (mRequireFace && mConsentFace == null) { + return new Intent(context, FaceEnrollParentalConsent.class); + } + if (mRequireFingerprint && mConsentFingerprint == null) { + return new Intent(context, FingerprintEnrollParentalConsent.class); + } + return null; + } + + /** + * Get the result of all consent requests. + * + * This should be called when {@link #launchNext(Activity, int, int, Intent)} returns false + * to indicate that all responses have been recorded. + * + * @return The aggregate consent status. + */ + @NonNull + public Bundle getConsentResult() { + final Bundle result = new Bundle(); + result.putBoolean(KEY_FACE_CONSENT, mConsentFace != null ? mConsentFace : false); + result.putBoolean(KEY_FINGERPRINT_CONSENT, + mConsentFingerprint != null ? mConsentFingerprint : false); + return result; + } + + /** @return If the result bundle contains consent for face authentication. */ + public static boolean hasFaceConsent(@NonNull Bundle bundle) { + return bundle.getBoolean(KEY_FACE_CONSENT, false); + } + + /** @return If the result bundle contains consent for fingerprint authentication. */ + public static boolean hasFingerprintConsent(@NonNull Bundle bundle) { + return bundle.getBoolean(KEY_FINGERPRINT_CONSENT, false); + } +} diff --git a/src/com/android/settings/biometrics/ParentalControlsUtils.java b/src/com/android/settings/biometrics/ParentalControlsUtils.java new file mode 100644 index 0000000000..0959184b16 --- /dev/null +++ b/src/com/android/settings/biometrics/ParentalControlsUtils.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2021 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.biometrics; + +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.content.Context; +import android.hardware.biometrics.BiometricAuthenticator; +import android.hardware.biometrics.ParentalControlsUtilsInternal; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settingslib.RestrictedLockUtils; + +/** + * Utilities for things at the cross-section of biometrics and parental controls. For example, + * determining if parental consent is required, determining which strings should be shown, etc. + */ +public class ParentalControlsUtils { + + private static final String TAG = "ParentalControlsUtils"; + + /** + * Public version that enables test paths, see + * {@link android.hardware.biometrics.ParentalControlsUtilsInternal#isTestModeEnabled(Context)} + * @return non-null EnforcedAdmin if parental consent is required + */ + public static RestrictedLockUtils.EnforcedAdmin parentConsentRequired(@NonNull Context context, + @BiometricAuthenticator.Modality int modality) { + + final UserHandle userHandle = new UserHandle(UserHandle.myUserId()); + if (ParentalControlsUtilsInternal.isTestModeEnabled(context)) { + Log.d(TAG, "Requiring consent for test flow"); + return new RestrictedLockUtils.EnforcedAdmin(null /* ComponentName */, + UserManager.DISALLOW_BIOMETRIC, userHandle); + } + + final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); + return parentConsentRequiredInternal(dpm, modality, userHandle); + } + + /** + * Internal testable version. + * @return non-null EnforcedAdmin if parental consent is required + */ + @Nullable + @VisibleForTesting + static RestrictedLockUtils.EnforcedAdmin parentConsentRequiredInternal( + @NonNull DevicePolicyManager dpm, @BiometricAuthenticator.Modality int modality, + @NonNull UserHandle userHandle) { + if (ParentalControlsUtilsInternal.parentConsentRequired(dpm, modality, + userHandle)) { + final ComponentName cn = + ParentalControlsUtilsInternal.getSupervisionComponentName(dpm, userHandle); + return new RestrictedLockUtils.EnforcedAdmin(cn, UserManager.DISALLOW_BIOMETRIC, + userHandle); + } else { + return null; + } + } +} diff --git a/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceController.java b/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceController.java index 6e989d8b5e..9723d92a03 100644 --- a/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceController.java +++ b/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceController.java @@ -16,15 +16,22 @@ package com.android.settings.biometrics.combination; import android.content.Context; +import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import androidx.annotation.Nullable; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; +import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; import com.android.settings.Settings; import com.android.settings.Utils; import com.android.settings.biometrics.BiometricStatusPreferenceController; +import com.android.settings.biometrics.ParentalControlsUtils; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedPreference; /** * Preference controller for biometrics settings page controlling the ability to unlock the phone @@ -38,6 +45,8 @@ public class CombinedBiometricStatusPreferenceController extends FingerprintManager mFingerprintManager; @Nullable FaceManager mFaceManager; + @VisibleForTesting + RestrictedPreference mPreference; public CombinedBiometricStatusPreferenceController(Context context) { this(context, KEY_BIOMETRIC_SETTINGS); @@ -50,6 +59,12 @@ public class CombinedBiometricStatusPreferenceController extends } @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(KEY_BIOMETRIC_SETTINGS); + } + + @Override protected boolean isDeviceSupported() { return Utils.hasFingerprintHardware(mContext) && Utils.hasFaceHardware(mContext); } @@ -60,6 +75,24 @@ public class CombinedBiometricStatusPreferenceController extends } @Override + public void updateState(Preference preference) { + super.updateState(preference); + // This controller currently is shown if fingerprint&face exist on the device. If this + // changes in the future, the modalities passed into the below will need to be updated. + final RestrictedLockUtils.EnforcedAdmin admin = ParentalControlsUtils + .parentConsentRequired(mContext, + BiometricAuthenticator.TYPE_FACE | BiometricAuthenticator.TYPE_FINGERPRINT); + updateStateInternal(admin); + } + + @VisibleForTesting + void updateStateInternal(@Nullable RestrictedLockUtils.EnforcedAdmin enforcedAdmin) { + if (enforcedAdmin != null && mPreference != null) { + mPreference.setDisabledByAdmin(enforcedAdmin); + } + } + + @Override protected String getSummaryTextEnrolled() { // Note that this is currently never called (see the super class) return mContext.getString( diff --git a/src/com/android/settings/biometrics/face/BiometricLockscreenBypassPreferenceController.java b/src/com/android/settings/biometrics/face/BiometricLockscreenBypassPreferenceController.java new file mode 100644 index 0000000000..7af7e3df9c --- /dev/null +++ b/src/com/android/settings/biometrics/face/BiometricLockscreenBypassPreferenceController.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 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.biometrics.face; + +import android.content.Context; + +import com.android.settings.Utils; + +/** + * Preference controller that controls whether unlocking directly to home. + */ +public class BiometricLockscreenBypassPreferenceController extends + FaceSettingsLockscreenBypassPreferenceController { + public BiometricLockscreenBypassPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + // When the device supports multiple biometrics auth, this preference will be shown + // in face unlock category. + if (Utils.isMultipleBiometricsSupported(mContext)) { + return AVAILABLE; + } + return UNSUPPORTED_ON_DEVICE; + } +} diff --git a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java index b29e2840e2..91cc3a9bfd 100644 --- a/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/face/FaceEnrollIntroduction.java @@ -26,6 +26,10 @@ import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; + import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.biometrics.BiometricEnrollIntroduction; @@ -34,7 +38,6 @@ import com.android.settings.overlay.FeatureFactory; import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settingslib.RestrictedLockUtilsInternal; -import com.google.android.setupcompat.template.FooterBarMixin; import com.google.android.setupcompat.template.FooterButton; import com.google.android.setupcompat.util.WizardManagerHelper; import com.google.android.setupdesign.span.LinkSpan; @@ -50,6 +53,8 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction { private FaceManager mFaceManager; private FaceFeatureProvider mFaceFeatureProvider; + @Nullable private FooterButton mPrimaryFooterButton; + @Nullable private FooterButton mSecondaryFooterButton; @Override protected void onCancelButtonClick(View view) { @@ -71,31 +76,71 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction { final ImageView iconGlasses = findViewById(R.id.icon_glasses); final ImageView iconLooking = findViewById(R.id.icon_looking); - final ImageView iconSecurity = findViewById(R.id.icon_security); iconGlasses.getBackground().setColorFilter(getIconColorFilter()); iconLooking.getBackground().setColorFilter(getIconColorFilter()); - iconSecurity.getBackground().setColorFilter(getIconColorFilter()); + + final TextView infoMessageGlasses = findViewById(R.id.info_message_glasses); + final TextView infoMessageLooking = findViewById(R.id.info_message_looking); + final TextView howMessage = findViewById(R.id.how_message); + final TextView inControlTitle = findViewById(R.id.title_in_control); + final TextView inControlMessage = findViewById(R.id.message_in_control); + infoMessageGlasses.setText(getInfoMessageGlasses()); + infoMessageLooking.setText(getInfoMessageLooking()); + howMessage.setText(getHowMessage()); + inControlTitle.setText(getInControlTitle()); + inControlMessage.setText(getInControlMessage()); mFaceManager = Utils.getFaceManagerOrNull(this); mFaceFeatureProvider = FeatureFactory.getFactory(getApplicationContext()) .getFaceFeatureProvider(); - // This path is an entry point for SetNewPasswordController, e.g. // adb shell am start -a android.app.action.SET_NEW_PASSWORD if (mToken == null && BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) { - mFooterBarMixin.getPrimaryButton().setEnabled(false); - // We either block on generateChallenge, or need to gray out the "next" button until - // the challenge is ready. Let's just do this for now. - mFaceManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> { - mToken = BiometricUtils.requestGatekeeperHat(this, getIntent(), mUserId, challenge); - mSensorId = sensorId; - mChallenge = challenge; - mFooterBarMixin.getPrimaryButton().setEnabled(true); - }); + if (generateChallengeOnCreate()) { + mFooterBarMixin.getPrimaryButton().setEnabled(false); + // We either block on generateChallenge, or need to gray out the "next" button until + // the challenge is ready. Let's just do this for now. + mFaceManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> { + mToken = BiometricUtils.requestGatekeeperHat(this, getIntent(), mUserId, + challenge); + mSensorId = sensorId; + mChallenge = challenge; + mFooterBarMixin.getPrimaryButton().setEnabled(true); + }); + } } } + protected boolean generateChallengeOnCreate() { + return true; + } + + @StringRes + protected int getInfoMessageGlasses() { + return R.string.security_settings_face_enroll_introduction_info_glasses; + } + + @StringRes + protected int getInfoMessageLooking() { + return R.string.security_settings_face_enroll_introduction_info_looking; + } + + @StringRes + protected int getHowMessage() { + return R.string.security_settings_face_enroll_introduction_how_message; + } + + @StringRes + protected int getInControlTitle() { + return R.string.security_settings_face_enroll_introduction_control_title; + } + + @StringRes + protected int getInControlMessage() { + return R.string.security_settings_face_enroll_introduction_control_message; + } + @Override protected boolean isDisabledByAdmin() { return RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled( @@ -207,38 +252,42 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction { } @Override + @NonNull protected FooterButton getPrimaryFooterButton() { - if (mFooterBarMixin == null) { - mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class); - } - - if (mFooterBarMixin.getPrimaryButton() == null) { - final FooterButton nextButtonBuilder = new FooterButton.Builder(this) + if (mPrimaryFooterButton == null) { + mPrimaryFooterButton = new FooterButton.Builder(this) .setText(R.string.security_settings_face_enroll_introduction_agree) .setButtonType(FooterButton.ButtonType.OPT_IN) .setListener(this::onNextButtonClick) .setTheme(R.style.SudGlifButton_Primary) .build(); - mFooterBarMixin.setPrimaryButton(nextButtonBuilder); } - return mFooterBarMixin.getPrimaryButton(); + return mPrimaryFooterButton; } @Override + @NonNull protected FooterButton getSecondaryFooterButton() { - if (mFooterBarMixin == null) { - mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class); - } - - if (mFooterBarMixin.getSecondaryButton() == null) { - final FooterButton noThanksButton = new FooterButton.Builder(this) + if (mSecondaryFooterButton == null) { + mSecondaryFooterButton = new FooterButton.Builder(this) .setText(R.string.security_settings_face_enroll_introduction_no_thanks) .setListener(this::onSkipButtonClick) .setButtonType(FooterButton.ButtonType.NEXT) .setTheme(R.style.SudGlifButton_Primary) .build(); - mFooterBarMixin.setSecondaryButton(noThanksButton, true /* usePrimaryStyle */); } - return mFooterBarMixin.getSecondaryButton(); + return mSecondaryFooterButton; + } + + @Override + @StringRes + protected int getAgreeButtonTextRes() { + return R.string.security_settings_fingerprint_enroll_introduction_agree; + } + + @Override + @StringRes + protected int getMoreButtonTextRes() { + return R.string.security_settings_face_enroll_introduction_more; } } diff --git a/src/com/android/settings/biometrics/face/FaceEnrollParentalConsent.java b/src/com/android/settings/biometrics/face/FaceEnrollParentalConsent.java new file mode 100644 index 0000000000..7a60a94ad5 --- /dev/null +++ b/src/com/android/settings/biometrics/face/FaceEnrollParentalConsent.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2021 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.biometrics.face; + +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; + +import android.app.settings.SettingsEnums; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; + +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; + +import com.android.settings.R; + +/** + * Displays parental consent information for face authentication. + */ +public class FaceEnrollParentalConsent extends FaceEnrollIntroduction { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setDescriptionText(R.string.security_settings_face_enroll_introduction_consent_message); + } + + @Override + protected void onNextButtonClick(View view) { + onConsentResult(true /* granted */); + } + + @Override + protected void onSkipButtonClick(View view) { + onConsentResult(false /* granted */); + } + + private void onConsentResult(boolean granted) { + final Intent result = new Intent(); + result.putExtra(EXTRA_KEY_MODALITY, TYPE_FACE); + setResult(granted ? RESULT_CONSENT_GRANTED : RESULT_CONSENT_DENIED, result); + finish(); + } + + @Override + protected boolean onSetOrConfirmCredentials(@Nullable Intent data) { + // prevent challenge from being generated by default + return true; + } + + @Override + protected boolean generateChallengeOnCreate() { + return false; + } + + @Override + @StringRes + protected int getInfoMessageGlasses() { + return R.string.security_settings_face_enroll_introduction_info_consent_glasses; + } + + @Override + @StringRes + protected int getInfoMessageLooking() { + return R.string.security_settings_face_enroll_introduction_info_consent_looking; + } + + @Override + @StringRes + protected int getHowMessage() { + return R.string.security_settings_face_enroll_introduction_how_consent_message; + } + + @Override + @StringRes + protected int getInControlTitle() { + return R.string.security_settings_face_enroll_introduction_control_consent_title; + } + + @Override + @StringRes + protected int getInControlMessage() { + return R.string.security_settings_face_enroll_introduction_control_consent_message; + } + + @Override + protected int getHeaderResDefault() { + return R.string.security_settings_face_enroll_consent_introduction_title; + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.FACE_PARENTAL_CONSENT; + } +} diff --git a/src/com/android/settings/biometrics/face/FaceSettingsLockscreenBypassPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsLockscreenBypassPreferenceController.java index 2f0ef4f123..c6ef87f92c 100644 --- a/src/com/android/settings/biometrics/face/FaceSettingsLockscreenBypassPreferenceController.java +++ b/src/com/android/settings/biometrics/face/FaceSettingsLockscreenBypassPreferenceController.java @@ -83,10 +83,10 @@ public class FaceSettingsLockscreenBypassPreferenceController @Override public int getAvailabilityStatus() { - // When the device supports multiple biometrics auth, this preference will be shown + // When the device supports multiple biometrics auth, this preference won't be shown // in face unlock category. if (Utils.isMultipleBiometricsSupported(mContext)) { - return AVAILABLE; + return UNSUPPORTED_ON_DEVICE; } if (mUserManager.isManagedProfile(UserHandle.myUserId())) { return UNSUPPORTED_ON_DEVICE; diff --git a/src/com/android/settings/biometrics/face/FaceStatusPreferenceController.java b/src/com/android/settings/biometrics/face/FaceStatusPreferenceController.java index 2ceaa0d7db..2b130ae244 100644 --- a/src/com/android/settings/biometrics/face/FaceStatusPreferenceController.java +++ b/src/com/android/settings/biometrics/face/FaceStatusPreferenceController.java @@ -17,18 +17,29 @@ package com.android.settings.biometrics.face; import android.content.Context; +import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.face.FaceManager; +import androidx.annotation.Nullable; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; import com.android.settings.Settings; import com.android.settings.Utils; import com.android.settings.biometrics.BiometricStatusPreferenceController; +import com.android.settings.biometrics.ParentalControlsUtils; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedPreference; public class FaceStatusPreferenceController extends BiometricStatusPreferenceController { public static final String KEY_FACE_SETTINGS = "face_settings"; protected final FaceManager mFaceManager; + @VisibleForTesting + RestrictedPreference mPreference; public FaceStatusPreferenceController(Context context) { this(context, KEY_FACE_SETTINGS); @@ -40,6 +51,12 @@ public class FaceStatusPreferenceController extends BiometricStatusPreferenceCon } @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(KEY_FACE_SETTINGS); + } + + @Override protected boolean isDeviceSupported() { return !Utils.isMultipleBiometricsSupported(mContext) && Utils.hasFaceHardware(mContext); } @@ -50,6 +67,21 @@ public class FaceStatusPreferenceController extends BiometricStatusPreferenceCon } @Override + public void updateState(Preference preference) { + super.updateState(preference); + final RestrictedLockUtils.EnforcedAdmin admin = ParentalControlsUtils + .parentConsentRequired(mContext, BiometricAuthenticator.TYPE_FACE); + updateStateInternal(admin); + } + + @VisibleForTesting + void updateStateInternal(@Nullable RestrictedLockUtils.EnforcedAdmin enforcedAdmin) { + if (enforcedAdmin != null && mPreference != null) { + mPreference.setDisabledByAdmin(enforcedAdmin); + } + } + + @Override protected String getSummaryTextEnrolled() { return mContext.getResources() .getString(R.string.security_settings_face_preference_summary); diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java index 8a070ab46b..8c3b1ceb89 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollIntroduction.java @@ -27,6 +27,10 @@ import android.util.Log; import android.widget.ImageView; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; + import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.biometrics.BiometricEnrollIntroduction; @@ -35,7 +39,6 @@ import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settingslib.HelpUtils; import com.android.settingslib.RestrictedLockUtilsInternal; -import com.google.android.setupcompat.template.FooterBarMixin; import com.google.android.setupcompat.template.FooterButton; import com.google.android.setupdesign.span.LinkSpan; @@ -46,6 +49,8 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction { private static final String TAG = "FingerprintIntro"; private FingerprintManager mFingerprintManager; + @Nullable private FooterButton mPrimaryFooterButton; + @Nullable private FooterButton mSecondaryFooterButton; @Override protected void onCreate(Bundle savedInstanceState) { @@ -59,21 +64,62 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction { super.onCreate(savedInstanceState); final ImageView iconFingerprint = findViewById(R.id.icon_fingerprint); - final ImageView iconLocked = findViewById(R.id.icon_locked); - final ImageView iconDelete = findViewById(R.id.icon_delete); final ImageView iconInfo = findViewById(R.id.icon_info); final ImageView iconLink = findViewById(R.id.icon_link); iconFingerprint.getDrawable().setColorFilter(getIconColorFilter()); - iconLocked.getDrawable().setColorFilter(getIconColorFilter()); - iconDelete.getDrawable().setColorFilter(getIconColorFilter()); iconInfo.getDrawable().setColorFilter(getIconColorFilter()); iconLink.getDrawable().setColorFilter(getIconColorFilter()); + + final TextView footerMessage2 = findViewById(R.id.footer_message_2); + final TextView footerMessage3 = findViewById(R.id.footer_message_3); + final TextView footerMessage4 = findViewById(R.id.footer_message_4); + final TextView footerMessage5 = findViewById(R.id.footer_message_5); + footerMessage2.setText(getFooterMessage2()); + footerMessage3.setText(getFooterMessage3()); + footerMessage4.setText(getFooterMessage4()); + footerMessage5.setText(getFooterMessage5()); + + final TextView footerTitle1 = findViewById(R.id.footer_title_1); + final TextView footerTitle2 = findViewById(R.id.footer_title_2); + footerTitle1.setText(getFooterTitle1()); + footerTitle2.setText(getFooterTitle2()); } + @StringRes int getNegativeButtonTextId() { return R.string.security_settings_fingerprint_enroll_introduction_skip; } + @StringRes + protected int getFooterTitle1() { + return R.string.security_settings_fingerprint_enroll_introduction_footer_title_1; + } + + @StringRes + protected int getFooterTitle2() { + return R.string.security_settings_fingerprint_enroll_introduction_footer_title_2; + } + + @StringRes + protected int getFooterMessage2() { + return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_2; + } + + @StringRes + protected int getFooterMessage3() { + return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_3; + } + + @StringRes + protected int getFooterMessage4() { + return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_4; + } + + @StringRes + protected int getFooterMessage5() { + return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_5; + } + @Override protected boolean isDisabledByAdmin() { return RestrictedLockUtilsInternal.checkIfKeyguardFeaturesDisabled( @@ -194,43 +240,42 @@ public class FingerprintEnrollIntroduction extends BiometricEnrollIntroduction { } @Override + @NonNull protected FooterButton getPrimaryFooterButton() { - if (mFooterBarMixin == null) { - mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class); - } - - if (mFooterBarMixin.getPrimaryButton() == null) { - final FooterButton nextButtonBuilder = new FooterButton.Builder(this) + if (mPrimaryFooterButton == null) { + mPrimaryFooterButton = new FooterButton.Builder(this) .setText(R.string.security_settings_fingerprint_enroll_introduction_agree) .setListener(this::onNextButtonClick) .setButtonType(FooterButton.ButtonType.OPT_IN) .setTheme(R.style.SudGlifButton_Primary) .build(); - mFooterBarMixin.setPrimaryButton(nextButtonBuilder); } - return mFooterBarMixin.getPrimaryButton(); + return mPrimaryFooterButton; } @Override + @NonNull protected FooterButton getSecondaryFooterButton() { - if (mFooterBarMixin == null) { - mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class); - } - - if (mFooterBarMixin.getSecondaryButton() == null) { - final FooterButton noThanksButton = new FooterButton.Builder(this) + if (mSecondaryFooterButton == null) { + mSecondaryFooterButton = new FooterButton.Builder(this) .setText(getNegativeButtonTextId()) .setListener(this::onSkipButtonClick) .setButtonType(FooterButton.ButtonType.NEXT) .setTheme(R.style.SudGlifButton_Primary) .build(); - mFooterBarMixin.setSecondaryButton(noThanksButton, true /* usePrimaryStyle */); } - return mFooterBarMixin.getSecondaryButton(); + return mSecondaryFooterButton; + } + + @Override + @StringRes + protected int getAgreeButtonTextRes() { + return R.string.security_settings_fingerprint_enroll_introduction_agree; } @Override - protected int getScrollCompletedText() { + @StringRes + protected int getMoreButtonTextRes() { return R.string.security_settings_face_enroll_introduction_more; } } diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollParentalConsent.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollParentalConsent.java new file mode 100644 index 0000000000..636e703bae --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollParentalConsent.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2021 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.biometrics.fingerprint; + +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; + +import android.app.settings.SettingsEnums; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; + +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; + +import com.android.settings.R; + +/** + * Displays parental consent information for fingerprint authentication. + */ +public class FingerprintEnrollParentalConsent extends FingerprintEnrollIntroduction { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setDescriptionText( + R.string.security_settings_fingerprint_enroll_introduction_consent_message); + } + + @Override + protected void onNextButtonClick(View view) { + onConsentResult(true /* granted */); + } + + @Override + protected void onSkipButtonClick(View view) { + onConsentResult(false /* granted */); + } + + private void onConsentResult(boolean granted) { + final Intent result = new Intent(); + result.putExtra(EXTRA_KEY_MODALITY, TYPE_FINGERPRINT); + setResult(granted ? RESULT_CONSENT_GRANTED : RESULT_CONSENT_DENIED, result); + finish(); + } + + @Override + protected boolean onSetOrConfirmCredentials(@Nullable Intent data) { + // prevent challenge from being generated by default + return true; + } + + @StringRes + @Override + protected int getFooterTitle1() { + return R.string.security_settings_fingerprint_enroll_introduction_footer_title_consent_1; + } + + @StringRes + @Override + protected int getFooterMessage2() { + return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_2; + } + + @StringRes + @Override + protected int getFooterMessage3() { + return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_3; + } + + @StringRes + protected int getFooterMessage4() { + return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_4; + } + + @StringRes + protected int getFooterMessage5() { + return R.string.security_settings_fingerprint_v2_enroll_introduction_footer_message_consent_5; + } + + @Override + protected int getHeaderResDefault() { + return R.string.security_settings_fingerprint_enroll_consent_introduction_title; + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.FINGERPRINT_PARENTAL_CONSENT; + } +} diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java b/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java index 416e805682..e4d86a199e 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java @@ -45,6 +45,8 @@ public class FingerprintErrorDialog extends BiometricErrorDialog { // This message happens when the underlying crypto layer decides to revoke the // enrollment auth token. return R.string.security_settings_fingerprint_enroll_error_timeout_dialog_message; + case FingerprintManager.FINGERPRINT_ERROR_BAD_CALIBARTION: + return R.string.security_settings_fingerprint_bad_calibration; default: // There's nothing specific to tell the user about. Ask them to try again. return R.string.security_settings_fingerprint_enroll_error_generic_dialog_message; diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintStatusPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintStatusPreferenceController.java index e53dbc411a..5166bae274 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintStatusPreferenceController.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintStatusPreferenceController.java @@ -17,17 +17,28 @@ package com.android.settings.biometrics.fingerprint; import android.content.Context; +import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.fingerprint.FingerprintManager; +import androidx.annotation.Nullable; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.biometrics.BiometricStatusPreferenceController; +import com.android.settings.biometrics.ParentalControlsUtils; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedPreference; public class FingerprintStatusPreferenceController extends BiometricStatusPreferenceController { private static final String KEY_FINGERPRINT_SETTINGS = "fingerprint_settings"; protected final FingerprintManager mFingerprintManager; + @VisibleForTesting + RestrictedPreference mPreference; public FingerprintStatusPreferenceController(Context context) { this(context, KEY_FINGERPRINT_SETTINGS); @@ -39,6 +50,12 @@ public class FingerprintStatusPreferenceController extends BiometricStatusPrefer } @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(KEY_FINGERPRINT_SETTINGS); + } + + @Override protected boolean isDeviceSupported() { return !Utils.isMultipleBiometricsSupported(mContext) && Utils.hasFingerprintHardware(mContext); @@ -50,6 +67,21 @@ public class FingerprintStatusPreferenceController extends BiometricStatusPrefer } @Override + public void updateState(Preference preference) { + super.updateState(preference); + final RestrictedLockUtils.EnforcedAdmin admin = ParentalControlsUtils + .parentConsentRequired(mContext, BiometricAuthenticator.TYPE_FINGERPRINT); + updateStateInternal(admin); + } + + @VisibleForTesting + void updateStateInternal(@Nullable RestrictedLockUtils.EnforcedAdmin enforcedAdmin) { + if (enforcedAdmin != null && mPreference != null) { + mPreference.setDisabledByAdmin(enforcedAdmin); + } + } + + @Override protected String getSummaryTextEnrolled() { final int numEnrolled = mFingerprintManager.getEnrolledFingerprints(getUserId()).size(); return mContext.getResources().getQuantityString( diff --git a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java index 5dd769db13..ce980e0bda 100644 --- a/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java +++ b/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragment.java @@ -50,11 +50,6 @@ public class ConnectedDeviceDashboardFragment extends DashboardFragment { } @Override - protected boolean isParalleledControllers() { - return true; - } - - @Override public int getHelpResource() { return R.string.help_url_connected_devices; } diff --git a/src/com/android/settings/core/CategoryMixin.java b/src/com/android/settings/core/CategoryMixin.java new file mode 100644 index 0000000000..8d0a412a60 --- /dev/null +++ b/src/com/android/settings/core/CategoryMixin.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2021 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.core; + +import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE; +import static androidx.lifecycle.Lifecycle.Event.ON_RESUME; + +import android.annotation.Nullable; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.AsyncTask; +import android.text.TextUtils; +import android.util.ArraySet; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; +import androidx.lifecycle.LifecycleObserver; +import androidx.lifecycle.OnLifecycleEvent; + +import com.android.settings.dashboard.CategoryManager; +import com.android.settingslib.drawer.Tile; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * A mixin that handles live categories for Injection + */ +public class CategoryMixin implements LifecycleObserver { + + private static final String TAG = "CategoryMixin"; + private static final String DATA_SCHEME_PKG = "package"; + + // Serves as a temporary list of tiles to ignore until we heard back from the PM that they + // are disabled. + private static final ArraySet<ComponentName> sTileDenylist = new ArraySet<>(); + + private final Context mContext; + private final PackageReceiver mPackageReceiver = new PackageReceiver(); + private final List<CategoryListener> mCategoryListeners = new ArrayList<>(); + private int mCategoriesUpdateTaskCount; + + public CategoryMixin(Context context) { + mContext = context; + } + + /** + * Resume Lifecycle event + */ + @OnLifecycleEvent(ON_RESUME) + public void onResume() { + final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addAction(Intent.ACTION_PACKAGE_REPLACED); + filter.addDataScheme(DATA_SCHEME_PKG); + mContext.registerReceiver(mPackageReceiver, filter); + + updateCategories(); + } + + /** + * Pause Lifecycle event + */ + @OnLifecycleEvent(ON_PAUSE) + public void onPause() { + mContext.unregisterReceiver(mPackageReceiver); + } + + /** + * Add a category listener + */ + public void addCategoryListener(CategoryListener listener) { + mCategoryListeners.add(listener); + } + + /** + * Remove a category listener + */ + public void removeCategoryListener(CategoryListener listener) { + mCategoryListeners.remove(listener); + } + + /** + * Updates dashboard categories. + */ + public void updateCategories() { + updateCategories(false /* fromBroadcast */); + } + + void addToDenylist(ComponentName component) { + sTileDenylist.add(component); + } + + void removeFromDenylist(ComponentName component) { + sTileDenylist.remove(component); + } + + @VisibleForTesting + void onCategoriesChanged(Set<String> categories) { + mCategoryListeners.forEach(listener -> listener.onCategoriesChanged(categories)); + } + + private void updateCategories(boolean fromBroadcast) { + // Only allow at most 2 tasks existing at the same time since when the first one is + // executing, there may be new data from the second update request. + // Ignore the third update request because the second task is still waiting for the first + // task to complete in a serial thread, which will get the latest data. + if (mCategoriesUpdateTaskCount < 2) { + new CategoriesUpdateTask().execute(fromBroadcast); + } + } + + /** + * A handler implementing a {@link CategoryMixin} + */ + public interface CategoryHandler { + /** returns a {@link CategoryMixin} */ + CategoryMixin getCategoryMixin(); + } + + /** + * A listener receiving category change events. + */ + public interface CategoryListener { + /** + * @param categories the changed categories that have to be refreshed, or null to force + * refreshing all. + */ + void onCategoriesChanged(@Nullable Set<String> categories); + } + + private class CategoriesUpdateTask extends AsyncTask<Boolean, Void, Set<String>> { + + private final CategoryManager mCategoryManager; + private Map<ComponentName, Tile> mPreviousTileMap; + + CategoriesUpdateTask() { + mCategoriesUpdateTaskCount++; + mCategoryManager = CategoryManager.get(mContext); + } + + @Override + protected Set<String> doInBackground(Boolean... params) { + mPreviousTileMap = mCategoryManager.getTileByComponentMap(); + mCategoryManager.reloadAllCategories(mContext); + mCategoryManager.updateCategoryFromDenylist(sTileDenylist); + return getChangedCategories(params[0]); + } + + @Override + protected void onPostExecute(Set<String> categories) { + if (categories == null || !categories.isEmpty()) { + onCategoriesChanged(categories); + } + mCategoriesUpdateTaskCount--; + } + + // Return the changed categories that have to be refreshed, or null to force refreshing all. + private Set<String> getChangedCategories(boolean fromBroadcast) { + if (!fromBroadcast) { + // Always refresh for non-broadcast case. + return null; + } + + final Set<String> changedCategories = new ArraySet<>(); + final Map<ComponentName, Tile> currentTileMap = + mCategoryManager.getTileByComponentMap(); + currentTileMap.forEach((component, currentTile) -> { + final Tile previousTile = mPreviousTileMap.get(component); + // Check if the tile is newly added. + if (previousTile == null) { + Log.i(TAG, "Tile added: " + component.flattenToShortString()); + changedCategories.add(currentTile.getCategory()); + return; + } + + // Check if the title or summary has changed. + if (!TextUtils.equals(currentTile.getTitle(mContext), + previousTile.getTitle(mContext)) + || !TextUtils.equals(currentTile.getSummary(mContext), + previousTile.getSummary(mContext))) { + Log.i(TAG, "Tile changed: " + component.flattenToShortString()); + changedCategories.add(currentTile.getCategory()); + } + }); + + // Check if any previous tile is removed. + final Set<ComponentName> removal = new ArraySet(mPreviousTileMap.keySet()); + removal.removeAll(currentTileMap.keySet()); + removal.forEach(component -> { + Log.i(TAG, "Tile removed: " + component.flattenToShortString()); + changedCategories.add(mPreviousTileMap.get(component).getCategory()); + }); + + return changedCategories; + } + } + + private class PackageReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + updateCategories(true /* fromBroadcast */); + } + } +} diff --git a/src/com/android/settings/core/SettingsBaseActivity.java b/src/com/android/settings/core/SettingsBaseActivity.java index 6dba83b8af..fb6b49f966 100644 --- a/src/com/android/settings/core/SettingsBaseActivity.java +++ b/src/com/android/settings/core/SettingsBaseActivity.java @@ -16,52 +16,39 @@ package com.android.settings.core; import android.annotation.LayoutRes; -import android.annotation.Nullable; import android.app.ActivityManager; -import android.app.ActivityOptions; -import android.content.BroadcastReceiver; import android.content.ComponentName; -import android.content.Context; import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.TypedArray; -import android.os.AsyncTask; import android.os.Bundle; -import android.os.UserHandle; import android.text.TextUtils; -import android.util.ArraySet; import android.util.Log; import android.view.LayoutInflater; -import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.widget.Toolbar; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.fragment.app.FragmentActivity; import com.android.settings.R; import com.android.settings.SubSettings; -import com.android.settings.Utils; -import com.android.settings.dashboard.CategoryManager; +import com.android.settings.core.CategoryMixin.CategoryHandler; import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin; -import com.android.settingslib.drawer.Tile; -import com.android.settingslib.transition.SettingsTransitionHelper; import com.android.settingslib.transition.SettingsTransitionHelper.TransitionType; +import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.appbar.CollapsingToolbarLayout; import com.google.android.material.resources.TextAppearanceConfig; import com.google.android.setupcompat.util.WizardManagerHelper; import com.google.android.setupdesign.util.ThemeHelper; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -public class SettingsBaseActivity extends FragmentActivity { +/** Base activity for Settings pages */ +public class SettingsBaseActivity extends FragmentActivity implements CategoryHandler { /** * What type of page transition should be apply. @@ -70,29 +57,20 @@ public class SettingsBaseActivity extends FragmentActivity { protected static final boolean DEBUG_TIMING = false; private static final String TAG = "SettingsBaseActivity"; - private static final String DATA_SCHEME_PKG = "package"; private static final int DEFAULT_REQUEST = -1; - // Serves as a temporary list of tiles to ignore until we heard back from the PM that they - // are disabled. - private static ArraySet<ComponentName> sTileDenylist = new ArraySet<>(); - - private final PackageReceiver mPackageReceiver = new PackageReceiver(); - private final List<CategoryListener> mCategoryListeners = new ArrayList<>(); - + protected CategoryMixin mCategoryMixin; protected CollapsingToolbarLayout mCollapsingToolbarLayout; - private int mCategoriesUpdateTaskCount; + protected AppBarLayout mAppBarLayout; private Toolbar mToolbar; @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - if (Utils.isPageTransitionEnabled(this)) { - // Enable Activity transitions - getWindow().requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS); - SettingsTransitionHelper.applyForwardTransition(this); - SettingsTransitionHelper.applyBackwardTransition(this); - } + public CategoryMixin getCategoryMixin() { + return mCategoryMixin; + } + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (isLockTaskModePinned() && !isSettingsRunOnTop()) { Log.w(TAG, "Devices lock task mode pinned."); @@ -102,6 +80,9 @@ public class SettingsBaseActivity extends FragmentActivity { getLifecycle().addObserver(new HideNonSystemOverlayMixin(this)); TextAppearanceConfig.setShouldLoadFontSynchronously(true); + mCategoryMixin = new CategoryMixin(this); + getLifecycle().addObserver(mCategoryMixin); + final TypedArray theme = getTheme().obtainStyledAttributes(android.R.styleable.Theme); if (!theme.getBoolean(android.R.styleable.Theme_windowNoTitle, false)) { requestWindowFeature(Window.FEATURE_NO_TITLE); @@ -118,6 +99,8 @@ public class SettingsBaseActivity extends FragmentActivity { if (isToolbarEnabled() && !isAnySetupWizard) { super.setContentView(R.layout.collapsing_toolbar_base_layout); mCollapsingToolbarLayout = findViewById(R.id.collapsing_toolbar); + mAppBarLayout = findViewById(R.id.app_bar); + disableCollapsingToolbarLayoutScrollingBehavior(); } else { super.setContentView(R.layout.settings_base_layout); } @@ -151,27 +134,9 @@ public class SettingsBaseActivity extends FragmentActivity { } @Override - public boolean onOptionsItemSelected(@NonNull MenuItem item) { - final int id = item.getItemId(); - if (id == android.R.id.home) { - // Make the up button behave the same as the back button. - finishAfterTransition(); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override public void startActivityForResult(Intent intent, int requestCode, @androidx.annotation.Nullable Bundle options) { final int transitionType = getTransitionType(intent); - if (Utils.isPageTransitionEnabled(this) && - transitionType == TransitionType.TRANSITION_SHARED_AXIS) { - super.startActivityForResult(intent, requestCode, - createActivityOptionsBundleForTransition(options)); - return; - } - super.startActivityForResult(intent, requestCode, options); if (transitionType == TransitionType.TRANSITION_SLIDE) { overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out); @@ -181,48 +146,14 @@ public class SettingsBaseActivity extends FragmentActivity { } @Override - public void startActivityForResultAsUser(Intent intent, int requestCode, - UserHandle userHandle) { - if (!Utils.isPageTransitionEnabled(this) || requestCode == DEFAULT_REQUEST) { - super.startActivityForResultAsUser(intent, requestCode, userHandle); - return; - } - super.startActivityForResultAsUser(intent, requestCode, - createActivityOptionsBundleForTransition(null), - userHandle); - } - - @Override - protected void onResume() { - super.onResume(); - final IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); - filter.addAction(Intent.ACTION_PACKAGE_REMOVED); - filter.addAction(Intent.ACTION_PACKAGE_CHANGED); - filter.addAction(Intent.ACTION_PACKAGE_REPLACED); - filter.addDataScheme(DATA_SCHEME_PKG); - registerReceiver(mPackageReceiver, filter); - - updateCategories(); - } - - @Override protected void onPause() { // For accessibility activities launched from setup wizard. if (getTransitionType(getIntent()) == TransitionType.TRANSITION_FADE) { overridePendingTransition(R.anim.sud_stay, android.R.anim.fade_out); } - unregisterReceiver(mPackageReceiver); super.onPause(); } - public void addCategoryListener(CategoryListener listener) { - mCategoryListeners.add(listener); - } - - public void remCategoryListener(CategoryListener listener) { - mCategoryListeners.remove(listener); - } - @Override public void setContentView(@LayoutRes int layoutResID) { final ViewGroup parent = findViewById(R.id.content_frame); @@ -270,13 +201,6 @@ public class SettingsBaseActivity extends FragmentActivity { return true; } - private void onCategoriesChanged(Set<String> categories) { - final int N = mCategoryListeners.size(); - for (int i = 0; i < N; i++) { - mCategoryListeners.get(i).onCategoriesChanged(categories); - } - } - private boolean isLockTaskModePinned() { final ActivityManager activityManager = getApplicationContext().getSystemService(ActivityManager.class); @@ -300,9 +224,9 @@ public class SettingsBaseActivity extends FragmentActivity { boolean isEnabled = state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED; if (isEnabled != enabled || state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) { if (enabled) { - sTileDenylist.remove(component); + mCategoryMixin.removeFromDenylist(component); } else { - sTileDenylist.add(component); + mCategoryMixin.addToDenylist(component); } pm.setComponentEnabledSetting(component, enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED @@ -313,126 +237,21 @@ public class SettingsBaseActivity extends FragmentActivity { return false; } - /** - * Updates dashboard categories. Only necessary to call this after setTileEnabled - */ - public void updateCategories() { - updateCategories(false /* fromBroadcast */); - } - - private void updateCategories(boolean fromBroadcast) { - // Only allow at most 2 tasks existing at the same time since when the first one is - // executing, there may be new data from the second update request. - // Ignore the third update request because the second task is still waiting for the first - // task to complete in a serial thread, which will get the latest data. - if (mCategoriesUpdateTaskCount < 2) { - new CategoriesUpdateTask().execute(fromBroadcast); - } + private void disableCollapsingToolbarLayoutScrollingBehavior() { + final CoordinatorLayout.LayoutParams params = + (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams(); + final AppBarLayout.Behavior behavior = new AppBarLayout.Behavior(); + behavior.setDragCallback( + new AppBarLayout.Behavior.DragCallback() { + @Override + public boolean canDrag(@NonNull AppBarLayout appBarLayout) { + return false; + } + }); + params.setBehavior(behavior); } private int getTransitionType(Intent intent) { - return intent.getIntExtra(EXTRA_PAGE_TRANSITION_TYPE, - SettingsTransitionHelper.TransitionType.TRANSITION_SHARED_AXIS); - } - - @androidx.annotation.Nullable - private Bundle createActivityOptionsBundleForTransition( - @androidx.annotation.Nullable Bundle options) { - if (mToolbar == null) { - Log.w(TAG, "setActionBar(Toolbar) is not called. Cannot apply settings transition!"); - return options; - } - final Bundle transitionOptions = ActivityOptions.makeSceneTransitionAnimation(this, - mToolbar, "shared_element_view").toBundle(); - if (options == null) { - return transitionOptions; - } - final Bundle mergedOptions = new Bundle(options); - mergedOptions.putAll(transitionOptions); - return mergedOptions; - } - - public interface CategoryListener { - /** - * @param categories the changed categories that have to be refreshed, or null to force - * refreshing all. - */ - void onCategoriesChanged(@Nullable Set<String> categories); - } - - private class CategoriesUpdateTask extends AsyncTask<Boolean, Void, Set<String>> { - - private final Context mContext; - private final CategoryManager mCategoryManager; - private Map<ComponentName, Tile> mPreviousTileMap; - - public CategoriesUpdateTask() { - mCategoriesUpdateTaskCount++; - mContext = SettingsBaseActivity.this; - mCategoryManager = CategoryManager.get(mContext); - } - - @Override - protected Set<String> doInBackground(Boolean... params) { - mPreviousTileMap = mCategoryManager.getTileByComponentMap(); - mCategoryManager.reloadAllCategories(mContext); - mCategoryManager.updateCategoryFromDenylist(sTileDenylist); - return getChangedCategories(params[0]); - } - - @Override - protected void onPostExecute(Set<String> categories) { - if (categories == null || !categories.isEmpty()) { - onCategoriesChanged(categories); - } - mCategoriesUpdateTaskCount--; - } - - // Return the changed categories that have to be refreshed, or null to force refreshing all. - private Set<String> getChangedCategories(boolean fromBroadcast) { - if (!fromBroadcast) { - // Always refresh for non-broadcast case. - return null; - } - - final Set<String> changedCategories = new ArraySet<>(); - final Map<ComponentName, Tile> currentTileMap = - mCategoryManager.getTileByComponentMap(); - currentTileMap.forEach((component, currentTile) -> { - final Tile previousTile = mPreviousTileMap.get(component); - // Check if the tile is newly added. - if (previousTile == null) { - Log.i(TAG, "Tile added: " + component.flattenToShortString()); - changedCategories.add(currentTile.getCategory()); - return; - } - - // Check if the title or summary has changed. - if (!TextUtils.equals(currentTile.getTitle(mContext), - previousTile.getTitle(mContext)) - || !TextUtils.equals(currentTile.getSummary(mContext), - previousTile.getSummary(mContext))) { - Log.i(TAG, "Tile changed: " + component.flattenToShortString()); - changedCategories.add(currentTile.getCategory()); - } - }); - - // Check if any previous tile is removed. - final Set<ComponentName> removal = new ArraySet(mPreviousTileMap.keySet()); - removal.removeAll(currentTileMap.keySet()); - removal.forEach(component -> { - Log.i(TAG, "Tile removed: " + component.flattenToShortString()); - changedCategories.add(mPreviousTileMap.get(component).getCategory()); - }); - - return changedCategories; - } - } - - private class PackageReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - updateCategories(true /* fromBroadcast */); - } + return intent.getIntExtra(EXTRA_PAGE_TRANSITION_TYPE, TransitionType.TRANSITION_NONE); } } diff --git a/src/com/android/settings/core/SubSettingLauncher.java b/src/com/android/settings/core/SubSettingLauncher.java index 0a6966fe17..616d72f3c2 100644 --- a/src/com/android/settings/core/SubSettingLauncher.java +++ b/src/com/android/settings/core/SubSettingLauncher.java @@ -28,7 +28,6 @@ import androidx.fragment.app.Fragment; import com.android.settings.SettingsActivity; import com.android.settings.SubSettings; -import com.android.settings.Utils; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.transition.SettingsTransitionHelper.TransitionType; @@ -182,10 +181,6 @@ public class SubSettingLauncher { @VisibleForTesting void launchAsUser(Intent intent, UserHandle userHandle) { - if (!Utils.isPageTransitionEnabled(mContext)) { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); - } mContext.startActivityAsUser(intent, userHandle); } diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java index e0c9820042..dfd931db7e 100644 --- a/src/com/android/settings/dashboard/DashboardFragment.java +++ b/src/com/android/settings/dashboard/DashboardFragment.java @@ -22,7 +22,6 @@ import android.content.Context; import android.os.Bundle; import android.text.TextUtils; import android.util.ArrayMap; -import android.util.FeatureFlagUtils; import android.util.Log; import androidx.annotation.CallSuper; @@ -36,9 +35,9 @@ import androidx.preference.SwitchPreference; import com.android.settings.R; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.core.BasePreferenceController; -import com.android.settings.core.FeatureFlags; +import com.android.settings.core.CategoryMixin.CategoryHandler; +import com.android.settings.core.CategoryMixin.CategoryListener; import com.android.settings.core.PreferenceControllerListHelper; -import com.android.settings.core.SettingsBaseActivity; import com.android.settings.overlay.FeatureFactory; import com.android.settings.widget.PrimarySwitchPreference; import com.android.settingslib.core.AbstractPreferenceController; @@ -63,8 +62,7 @@ import java.util.concurrent.ExecutionException; * Base fragment for dashboard style UI containing a list of static and dynamic setting items. */ public abstract class DashboardFragment extends SettingsPreferenceFragment - implements SettingsBaseActivity.CategoryListener, Indexable, - PreferenceGroup.OnExpandButtonClickListener, + implements CategoryListener, Indexable, PreferenceGroup.OnExpandButtonClickListener, BasePreferenceController.UiBlockListener { public static final String CATEGORY = "category"; private static final String TAG = "DashboardFragment"; @@ -200,9 +198,9 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment return; } final Activity activity = getActivity(); - if (activity instanceof SettingsBaseActivity) { + if (activity instanceof CategoryHandler) { mListeningToCategoryChange = true; - ((SettingsBaseActivity) activity).addCategoryListener(this); + ((CategoryHandler) activity).getCategoryMixin().addCategoryListener(this); } final ContentResolver resolver = getContentResolver(); mDashboardTilePrefKeys.values().stream() @@ -245,8 +243,8 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment unregisterDynamicDataObservers(new ArrayList<>(mRegisteredObservers)); if (mListeningToCategoryChange) { final Activity activity = getActivity(); - if (activity instanceof SettingsBaseActivity) { - ((SettingsBaseActivity) activity).remCategoryListener(this); + if (activity instanceof CategoryHandler) { + ((CategoryHandler) activity).getCategoryMixin().removeCategoryListener(this); } mListeningToCategoryChange = false; } @@ -360,11 +358,6 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment * Update state of each preference managed by PreferenceController. */ protected void updatePreferenceStates() { - if (isParalleledControllers() && FeatureFlagUtils.isEnabled(getContext(), - FeatureFlags.CONTROLLER_ENHANCEMENT)) { - updatePreferenceStatesInParallel(); - return; - } final PreferenceScreen screen = getPreferenceScreen(); Collection<List<AbstractPreferenceController>> controllerLists = mPreferenceControllers.values(); @@ -396,6 +389,8 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment * Use parallel method to update state of each preference managed by PreferenceController. */ @VisibleForTesting + // To use this parallel approach will cause the side effect of the UI flicker. Such as + // the thumb sliding of the toggle button. void updatePreferenceStatesInParallel() { final PreferenceScreen screen = getPreferenceScreen(); final Collection<List<AbstractPreferenceController>> controllerLists = diff --git a/src/com/android/settings/datausage/AppDataUsage.java b/src/com/android/settings/datausage/AppDataUsage.java index 5c5900e8f3..460f3909f7 100644 --- a/src/com/android/settings/datausage/AppDataUsage.java +++ b/src/com/android/settings/datausage/AppDataUsage.java @@ -39,10 +39,8 @@ import androidx.loader.content.Loader; import androidx.preference.Preference; import androidx.preference.Preference.OnPreferenceChangeListener; import androidx.preference.PreferenceCategory; -import androidx.recyclerview.widget.RecyclerView; import com.android.settings.R; -import com.android.settings.Utils; import com.android.settings.applications.AppInfoBase; import com.android.settings.widget.EntityHeaderController; import com.android.settingslib.AppItem; @@ -223,14 +221,6 @@ public class AppDataUsage extends DataUsageBaseFragment implements OnPreferenceC } LoaderManager.getInstance(this).restartLoader(LOADER_APP_USAGE_DATA, null /* args */, mUidDataCallbacks); - - if (Utils.isPageTransitionEnabled(mContext)) { - final RecyclerView recyclerView = getListView(); - if (recyclerView != null) { - recyclerView.setItemAnimator(null); - } - } - updatePrefs(); } diff --git a/src/com/android/settings/datetime/timezone/BaseTimeZonePicker.java b/src/com/android/settings/datetime/timezone/BaseTimeZonePicker.java index 6032abd9b1..6ce9f6f9ae 100644 --- a/src/com/android/settings/datetime/timezone/BaseTimeZonePicker.java +++ b/src/com/android/settings/datetime/timezone/BaseTimeZonePicker.java @@ -27,6 +27,8 @@ import android.widget.LinearLayout; import android.widget.SearchView; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.view.ViewCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -95,6 +97,7 @@ public abstract class BaseTimeZonePicker extends InstrumentedFragment LinearLayoutManager.VERTICAL, /* reverseLayout */ false)); mRecyclerView.setAdapter(mAdapter); mAppBarLayout = getActivity().findViewById(R.id.app_bar); + disableToolBarScrollableBehavior(); // Initialize TimeZoneDataLoader only when mRecyclerView is ready to avoid race // during onDateLoaderReady callback. @@ -160,13 +163,15 @@ public abstract class BaseTimeZonePicker extends InstrumentedFragment public boolean onMenuItemActionExpand(MenuItem item) { // To prevent a large space on tool bar. mAppBarLayout.setExpanded(false /*expanded*/, false /*animate*/); - // To prevent user can expand the collpasing tool bar view. + // To prevent user can expand the collapsing tool bar view. ViewCompat.setNestedScrollingEnabled(mRecyclerView, false); return true; } @Override public boolean onMenuItemActionCollapse(MenuItem item) { + // We keep the collapsed status after user cancel the search function. + mAppBarLayout.setExpanded(false /*expanded*/, false /*animate*/); ViewCompat.setNestedScrollingEnabled(mRecyclerView, true); return true; } @@ -188,4 +193,17 @@ public abstract class BaseTimeZonePicker extends InstrumentedFragment void onListItemClick(T item); } + private void disableToolBarScrollableBehavior() { + CoordinatorLayout.LayoutParams params = + (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams(); + AppBarLayout.Behavior behavior = new AppBarLayout.Behavior(); + behavior.setDragCallback( + new AppBarLayout.Behavior.DragCallback() { + @Override + public boolean canDrag(@NonNull AppBarLayout appBarLayout) { + return false; + } + }); + params.setBehavior(behavior); + } } diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index 7afce83a7d..fbab1fd124 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -402,11 +402,6 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra return mPreferenceControllers; } - @Override - protected boolean isParalleledControllers() { - return true; - } - private void registerReceivers() { LocalBroadcastManager.getInstance(getContext()) .registerReceiver(mEnableAdbReceiver, new IntentFilter( diff --git a/src/com/android/settings/development/transcode/TranscodeNotificationPreferenceController.java b/src/com/android/settings/development/transcode/TranscodeNotificationPreferenceController.java index e51f8ad5b6..fd5ec5aed3 100644 --- a/src/com/android/settings/development/transcode/TranscodeNotificationPreferenceController.java +++ b/src/com/android/settings/development/transcode/TranscodeNotificationPreferenceController.java @@ -37,7 +37,7 @@ public class TranscodeNotificationPreferenceController extends TogglePreferenceC @Override public boolean isChecked() { - return SystemProperties.getBoolean(TRANSCODE_NOTIFICATION_SYS_PROP_KEY, true); + return SystemProperties.getBoolean(TRANSCODE_NOTIFICATION_SYS_PROP_KEY, false); } @Override diff --git a/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java b/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java index 0a67ef29a2..611ee24baa 100644 --- a/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java +++ b/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java @@ -122,7 +122,7 @@ public class StorageAsyncLoader UserHandle.of(userId)); } catch (NameNotFoundException e) { Log.e(TAG, "Not able to get Context for user ID " + userId); - return 0; + return 0L; } try (Cursor cursor = perUserContext.getContentResolver().query( @@ -131,9 +131,9 @@ public class StorageAsyncLoader queryArgs, null /* cancellationSignal */)) { if (cursor == null) { - return 0; + return 0L; } - return cursor.moveToFirst() ? cursor.getInt(0) : 0; + return cursor.moveToFirst() ? cursor.getLong(0) : 0L; } } diff --git a/src/com/android/settings/display/AdaptiveSleepPreferenceController.java b/src/com/android/settings/display/AdaptiveSleepPreferenceController.java index 356c76f47e..b21c6ac32e 100644 --- a/src/com/android/settings/display/AdaptiveSleepPreferenceController.java +++ b/src/com/android/settings/display/AdaptiveSleepPreferenceController.java @@ -90,6 +90,7 @@ public class AdaptiveSleepPreferenceController { if (enforcedAdmin != null) { mPreference.setDisabledByAdmin(enforcedAdmin); } else { + mPreference.setChecked(isChecked()); mPreference.setEnabled(hasSufficientPermission(mPackageManager) && !isCameraLocked() && !isPowerSaveMode()); } diff --git a/src/com/android/settings/display/ScreenTimeoutSettings.java b/src/com/android/settings/display/ScreenTimeoutSettings.java index f79dc07ee0..18d10491e9 100644 --- a/src/com/android/settings/display/ScreenTimeoutSettings.java +++ b/src/com/android/settings/display/ScreenTimeoutSettings.java @@ -81,6 +81,7 @@ public class ScreenTimeoutSettings extends RadioButtonPickerFragment implements @Override public void onReceive(Context context, Intent intent) { mAdaptiveSleepBatterySaverPreferenceController.updateVisibility(); + mAdaptiveSleepController.updatePreference(); } }; diff --git a/src/com/android/settings/emergency/MoreSettingsPreferenceController.java b/src/com/android/settings/emergency/MoreSettingsPreferenceController.java index 3c9ae6ed10..cbb95a6357 100644 --- a/src/com/android/settings/emergency/MoreSettingsPreferenceController.java +++ b/src/com/android/settings/emergency/MoreSettingsPreferenceController.java @@ -43,6 +43,7 @@ import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.core.BasePreferenceController; +import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.widget.LayoutPreference; import java.util.List; @@ -57,6 +58,7 @@ public class MoreSettingsPreferenceController extends BasePreferenceController i private static final String TAG = "MoreSettingsPrefCtrl"; @VisibleForTesting Intent mIntent; + private LayoutPreference mPreference; public MoreSettingsPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); @@ -82,8 +84,8 @@ public class MoreSettingsPreferenceController extends BasePreferenceController i @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); - final LayoutPreference pref = screen.findPreference(getPreferenceKey()); - final Button button = pref.findViewById(R.id.button); + mPreference = screen.findPreference(getPreferenceKey()); + final Button button = mPreference.findViewById(R.id.button); final Drawable icon = getIcon(); button.setText(getButtonText()); if (icon != null) { @@ -109,6 +111,8 @@ public class MoreSettingsPreferenceController extends BasePreferenceController i @Override public void onClick(View v) { + FeatureFactory.getFactory(mContext).getMetricsFeatureProvider() + .logClickedPreference(mPreference, getMetricsCategory()); final Intent intent = new Intent(mIntent) .addCategory(Intent.CATEGORY_LAUNCHER) .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); @@ -170,5 +174,4 @@ public class MoreSettingsPreferenceController extends BasePreferenceController i icon.draw(canvas); return bitmap; } - } diff --git a/src/com/android/settings/enterprise/ActionDisabledByAdminDialog.java b/src/com/android/settings/enterprise/ActionDisabledByAdminDialog.java index 717c5bcd75..ce7ad24205 100644 --- a/src/com/android/settings/enterprise/ActionDisabledByAdminDialog.java +++ b/src/com/android/settings/enterprise/ActionDisabledByAdminDialog.java @@ -37,7 +37,7 @@ public class ActionDisabledByAdminDialog extends Activity final RestrictedLockUtils.EnforcedAdmin enforcedAdmin = getAdminDetailsFromIntent(getIntent()); final String restriction = getRestrictionFromIntent(getIntent()); - mDialogHelper = new ActionDisabledByAdminDialogHelper(this); + mDialogHelper = new ActionDisabledByAdminDialogHelper(this, restriction); mDialogHelper.prepareDialogBuilder(restriction, enforcedAdmin) .setOnDismissListener(this) .show(); diff --git a/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelper.java b/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelper.java index 5da076736e..ccc74367cd 100644 --- a/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelper.java +++ b/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelper.java @@ -56,11 +56,16 @@ public final class ActionDisabledByAdminDialogHelper { private final Activity mActivity; public ActionDisabledByAdminDialogHelper(Activity activity) { + this(activity, null /* restriction */); + } + + public ActionDisabledByAdminDialogHelper(Activity activity, String restriction) { mActivity = activity; mDialogView = (ViewGroup) LayoutInflater.from(mActivity).inflate( R.layout.admin_support_details_dialog, null); mActionDisabledByAdminController = ActionDisabledByAdminControllerFactory - .createInstance(mActivity, new DeviceAdminStringProviderImpl(mActivity)); + .createInstance(mActivity, restriction, + new DeviceAdminStringProviderImpl(mActivity)); } private @UserIdInt int getEnforcementAdminUserId(@NonNull EnforcedAdmin admin) { @@ -74,7 +79,9 @@ public final class ActionDisabledByAdminDialogHelper { public AlertDialog.Builder prepareDialogBuilder(String restriction, EnforcedAdmin enforcedAdmin) { AlertDialog.Builder builder = new AlertDialog.Builder(mActivity) - .setPositiveButton(R.string.okay, null) + .setPositiveButton(R.string.okay, + mActionDisabledByAdminController + .getPositiveButtonListener(mActivity, enforcedAdmin)) .setView(mDialogView); prepareDialogBuilder(builder, restriction, enforcedAdmin); return builder; diff --git a/src/com/android/settings/enterprise/DeviceAdminStringProviderImpl.java b/src/com/android/settings/enterprise/DeviceAdminStringProviderImpl.java index 68b202108b..5d11d4ab3a 100644 --- a/src/com/android/settings/enterprise/DeviceAdminStringProviderImpl.java +++ b/src/com/android/settings/enterprise/DeviceAdminStringProviderImpl.java @@ -79,4 +79,14 @@ class DeviceAdminStringProviderImpl implements DeviceAdminStringProvider { public String getDisabledByPolicyTitleForFinancedDevice() { return mContext.getString(R.string.disabled_by_policy_title_financed_device); } + + @Override + public String getDisabledBiometricsParentConsentTitle() { + return mContext.getString(R.string.disabled_by_policy_title_biometric_parental_consent); + } + + @Override + public String getDisabledBiometricsParentConsentContent() { + return mContext.getString(R.string.disabled_by_policy_content_biometric_parental_consent); + } } diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java index ca228e6821..3319e1b149 100644 --- a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java +++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java @@ -157,7 +157,8 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements /** Launches battery details page for an individual battery consumer. */ public static void startBatteryDetailPage(Activity caller, - InstrumentedPreferenceFragment fragment, BatteryEntry entry, String usagePercent) { + InstrumentedPreferenceFragment fragment, BatteryEntry entry, String usagePercent, + boolean isValidToShowSummary) { final LaunchBatteryDetailPageArgs launchArgs = new LaunchBatteryDetailPageArgs(); // configure the launch argument. launchArgs.mUsagePercent = usagePercent; @@ -166,8 +167,8 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements launchArgs.mUid = entry.getUid(); launchArgs.mIconId = entry.iconId; launchArgs.mConsumedPower = (int) entry.getConsumedPower(); - launchArgs.mForegroundTimeMs = entry.getTimeInForegroundMs(); - launchArgs.mBackgroundTimeMs = entry.getTimeInBackgroundMs(); + launchArgs.mForegroundTimeMs = isValidToShowSummary ? entry.getTimeInForegroundMs() : 0; + launchArgs.mBackgroundTimeMs = isValidToShowSummary ? entry.getTimeInBackgroundMs() : 0; launchArgs.mIsUserEntry = entry.isUserEntry(); startBatteryDetailPage(caller, fragment, launchArgs); } diff --git a/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java b/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java index 1dc572d91a..29872ac4d4 100644 --- a/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BatteryAppListPreferenceController.java @@ -188,7 +188,7 @@ public class BatteryAppListPreferenceController extends AbstractPreferenceContro PowerGaugePreference pgp = (PowerGaugePreference) preference; BatteryEntry entry = pgp.getInfo(); AdvancedPowerUsageDetail.startBatteryDetailPage(mActivity, - mFragment, entry, pgp.getPercent()); + mFragment, entry, pgp.getPercent(), /*isValidToShowSummary=*/ true); return true; } return false; @@ -359,9 +359,7 @@ public class BatteryAppListPreferenceController extends AbstractPreferenceContro componentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + deviceConsumer.getCustomPowerComponentCount(); componentId++) { - if (!showAllApps - && mBatteryUtils.shouldHideCustomDevicePowerComponent(deviceConsumer, - componentId)) { + if (!showAllApps) { continue; } diff --git a/src/com/android/settings/fuelgauge/BatteryUtils.java b/src/com/android/settings/fuelgauge/BatteryUtils.java index 52a4629a7e..d521ed166a 100644 --- a/src/com/android/settings/fuelgauge/BatteryUtils.java +++ b/src/com/android/settings/fuelgauge/BatteryUtils.java @@ -81,8 +81,6 @@ public class BatteryUtils { private static final String TAG = "BatteryUtils"; - private static final double MIN_POWER_THRESHOLD_MILLI_AMP_HOURS = 0.002; - private static BatteryUtils sInstance; private PackageManager mPackageManager; @@ -180,8 +178,7 @@ public class BatteryUtils { * battery consumption list. */ public boolean shouldHideUidBatteryConsumer(UidBatteryConsumer consumer, String[] packages) { - return consumer.getConsumedPower() < MIN_POWER_THRESHOLD_MILLI_AMP_HOURS - || mPowerUsageFeatureProvider.isTypeSystem(consumer.getUid(), packages) + return mPowerUsageFeatureProvider.isTypeSystem(consumer.getUid(), packages) || shouldHideUidBatteryConsumerUnconditionally(consumer, packages); } @@ -208,22 +205,11 @@ public class BatteryUtils { case BatteryConsumer.POWER_COMPONENT_WIFI: return true; default: - return consumer.getConsumedPower(powerComponentId) - < MIN_POWER_THRESHOLD_MILLI_AMP_HOURS; + return false; } } /** - * Returns true if the specified device custom power component should be excluded from the - * summary battery consumption list. - */ - public boolean shouldHideCustomDevicePowerComponent(BatteryConsumer consumer, - int customPowerComponentId) { - return consumer.getConsumedPowerForCustomComponent(customPowerComponentId) - < MIN_POWER_THRESHOLD_MILLI_AMP_HOURS; - } - - /** * Returns true if one the specified packages belongs to a hidden system module. */ public boolean isHiddenSystemModule(String[] packages) { diff --git a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSeekBarController.java b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSeekBarController.java index 3e62ea1450..173d946761 100644 --- a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSeekBarController.java +++ b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleSeekBarController.java @@ -55,7 +55,8 @@ public class BatterySaverScheduleSeekBarController implements public BatterySaverScheduleSeekBarController(Context context) { mContext = context; mSeekBarPreference = new SeekBarPreference(context); - mSeekBarPreference.setLayoutResource(R.layout.battery_saver_schedule_percentage_seekbar); + mSeekBarPreference.setLayoutResource(R.layout.preference_widget_seekbar_settings); + mSeekBarPreference.setIconSpaceReserved(false); mSeekBarPreference.setOnPreferenceChangeListener(this); mSeekBarPreference.setContinuousUpdates(true); mSeekBarPreference.setMax(MAX_SEEKBAR_VALUE); @@ -71,8 +72,9 @@ public class BatterySaverScheduleSeekBarController implements final int percentage = ((Integer) newValue) * 5; Settings.Global.putInt(mContext.getContentResolver(), Global.LOW_POWER_MODE_TRIGGER_LEVEL, percentage); - preference.setTitle(mContext.getString( - R.string.battery_saver_seekbar_title, Utils.formatPercentage(percentage))); + final CharSequence stateDescription = formatStateDescription(percentage); + preference.setTitle(stateDescription); + mSeekBarPreference.overrideSeekBarStateDescription(stateDescription); return true; } @@ -92,9 +94,10 @@ public class BatterySaverScheduleSeekBarController implements final int currentSeekbarValue = Math.max(threshold / 5, MIN_SEEKBAR_VALUE); mSeekBarPreference.setVisible(true); mSeekBarPreference.setProgress(currentSeekbarValue); - mSeekBarPreference.setTitle(mContext.getString( - R.string.battery_saver_seekbar_title, - Utils.formatPercentage(currentSeekbarValue * 5))); + final CharSequence stateDescription = formatStateDescription( + currentSeekbarValue * 5); + mSeekBarPreference.setTitle(stateDescription); + mSeekBarPreference.overrideSeekBarStateDescription(stateDescription); } } else { mSeekBarPreference.setVisible(false); @@ -112,4 +115,9 @@ public class BatterySaverScheduleSeekBarController implements mSeekBarPreference.setOrder(100); screen.addPreference(mSeekBarPreference); } + + private CharSequence formatStateDescription(int percentage) { + return mContext.getString(R.string.battery_saver_seekbar_title, + Utils.formatPercentage(percentage)); + } } diff --git a/src/com/android/settings/gestures/GesturePreferenceController.java b/src/com/android/settings/gestures/GesturePreferenceController.java index 4fcf276375..c771abdce2 100644 --- a/src/com/android/settings/gestures/GesturePreferenceController.java +++ b/src/com/android/settings/gestures/GesturePreferenceController.java @@ -42,7 +42,10 @@ public abstract class GesturePreferenceController extends TogglePreferenceContro public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); if (isAvailable()) { - mVideoPreference = screen.findPreference(getVideoPrefKey()); + final Preference pref = screen.findPreference(getVideoPrefKey()); + if (pref instanceof VideoPreference) { + mVideoPreference = screen.findPreference(getVideoPrefKey()); + } } } diff --git a/src/com/android/settings/gestures/OneHandedActionPullDownPrefController.java b/src/com/android/settings/gestures/OneHandedActionPullDownPrefController.java new file mode 100644 index 0000000000..84ea8b6c45 --- /dev/null +++ b/src/com/android/settings/gestures/OneHandedActionPullDownPrefController.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2021 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.gestures; + +import android.content.Context; +import android.net.Uri; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; +import com.android.settingslib.widget.RadioButtonPreference; + +/** + * The controller to handle one-handed mode pull screen into reach preference. + **/ +public class OneHandedActionPullDownPrefController extends BasePreferenceController + implements OneHandedSettingsUtils.TogglesCallback, LifecycleObserver, OnStart, OnStop { + + private final OneHandedSettingsUtils mUtils; + + private Preference mPreference; + + public OneHandedActionPullDownPrefController(Context context, String key) { + super(context, key); + mUtils = new OneHandedSettingsUtils(context); + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + if (preference instanceof RadioButtonPreference) { + ((RadioButtonPreference) preference).setChecked( + !OneHandedSettingsUtils.isSwipeDownNotificationEnabled(mContext)); + } + } + + @Override + public int getAvailabilityStatus() { + return (OneHandedSettingsUtils.isSupportOneHandedMode() + && OneHandedSettingsUtils.canEnableController(mContext)) + ? AVAILABLE : DISABLED_DEPENDENT_SETTING; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!getPreferenceKey().equals(preference.getKey())) { + return false; + } + OneHandedSettingsUtils.setSwipeDownNotificationEnabled(mContext, false); + if (preference instanceof RadioButtonPreference) { + ((RadioButtonPreference) preference).setChecked(true); + } + return true; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + } + + @Override + public void onStart() { + mUtils.registerToggleAwareObserver(this); + } + + @Override + public void onStop() { + mUtils.unregisterToggleAwareObserver(); + } + + @Override + public void onChange(Uri uri) { + if (mPreference == null) { + return; + } + if (uri.equals(OneHandedSettingsUtils.ONE_HANDED_MODE_ENABLED_URI)) { + mPreference.setEnabled(OneHandedSettingsUtils.canEnableController(mContext)); + } else if (uri.equals(OneHandedSettingsUtils.SHOW_NOTIFICATION_ENABLED_URI)) { + updateState(mPreference); + } + } +} diff --git a/src/com/android/settings/gestures/OneHandedActionShowNotificationPrefController.java b/src/com/android/settings/gestures/OneHandedActionShowNotificationPrefController.java new file mode 100644 index 0000000000..564429986e --- /dev/null +++ b/src/com/android/settings/gestures/OneHandedActionShowNotificationPrefController.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2021 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.gestures; + +import android.content.Context; +import android.net.Uri; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.core.lifecycle.events.OnStop; +import com.android.settingslib.widget.RadioButtonPreference; + +/** + * The controller to handle one-handed mode show notification preference. + **/ +public class OneHandedActionShowNotificationPrefController extends BasePreferenceController + implements OneHandedSettingsUtils.TogglesCallback, LifecycleObserver, OnStart, OnStop { + + private final OneHandedSettingsUtils mUtils; + + private Preference mPreference; + + public OneHandedActionShowNotificationPrefController(Context context, String key) { + super(context, key); + mUtils = new OneHandedSettingsUtils(context); + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + if (preference instanceof RadioButtonPreference) { + ((RadioButtonPreference) preference).setChecked( + OneHandedSettingsUtils.isSwipeDownNotificationEnabled(mContext)); + } + } + + @Override + public int getAvailabilityStatus() { + return (OneHandedSettingsUtils.isSupportOneHandedMode() + && OneHandedSettingsUtils.canEnableController(mContext)) + ? AVAILABLE : DISABLED_DEPENDENT_SETTING; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!getPreferenceKey().equals(preference.getKey())) { + return false; + } + OneHandedSettingsUtils.setSwipeDownNotificationEnabled(mContext, true); + if (preference instanceof RadioButtonPreference) { + ((RadioButtonPreference) preference).setChecked(true); + } + return true; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + } + + @Override + public void onStart() { + mUtils.registerToggleAwareObserver(this); + } + + @Override + public void onStop() { + mUtils.unregisterToggleAwareObserver(); + } + + @Override + public void onChange(Uri uri) { + if (mPreference == null) { + return; + } + if (uri.equals(OneHandedSettingsUtils.ONE_HANDED_MODE_ENABLED_URI)) { + mPreference.setEnabled(OneHandedSettingsUtils.canEnableController(mContext)); + } else if (uri.equals(OneHandedSettingsUtils.SHOW_NOTIFICATION_ENABLED_URI)) { + updateState(mPreference); + } + } +} diff --git a/src/com/android/settings/gestures/OneHandedEnablePreferenceController.java b/src/com/android/settings/gestures/OneHandedEnablePreferenceController.java index 1e9c240115..bd8de9fdad 100644 --- a/src/com/android/settings/gestures/OneHandedEnablePreferenceController.java +++ b/src/com/android/settings/gestures/OneHandedEnablePreferenceController.java @@ -17,40 +17,32 @@ package com.android.settings.gestures; import android.content.Context; +import android.provider.Settings; import com.android.settings.R; -import com.android.settings.widget.SettingsMainSwitchPreferenceController; +import com.android.settings.core.BasePreferenceController; /** - * The controller to handle one-handed mode enable or disable state. - **/ -public class OneHandedEnablePreferenceController extends SettingsMainSwitchPreferenceController { + * Preference controller for One-handed mode shortcut settings + */ +public class OneHandedEnablePreferenceController extends BasePreferenceController { - public OneHandedEnablePreferenceController(Context context, String key) { - super(context, key); - } + private static final String ONE_HANDED_ENABLED = Settings.Secure.ONE_HANDED_MODE_ENABLED; - @Override - public int getAvailabilityStatus() { - return OneHandedSettingsUtils.isFeatureAvailable(mContext) - ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + public OneHandedEnablePreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); } @Override - public boolean setChecked(boolean isChecked) { - OneHandedSettingsUtils.setOneHandedModeEnabled(mContext, isChecked); - OneHandedSettingsUtils.setSwipeDownNotificationEnabled(mContext, !isChecked); - return true; - } - - @Override - public boolean isChecked() { - return OneHandedSettingsUtils.isOneHandedModeEnabled(mContext); + public int getAvailabilityStatus() { + return OneHandedSettingsUtils.isSupportOneHandedMode() ? AVAILABLE : UNSUPPORTED_ON_DEVICE; } @Override public CharSequence getSummary() { return mContext.getText( - isChecked() ? R.string.gesture_setting_on : R.string.gesture_setting_off); + OneHandedSettingsUtils.isOneHandedModeEnabled(mContext) + ? R.string.gesture_setting_on : R.string.gesture_setting_off); } + } diff --git a/src/com/android/settings/gestures/OneHandedAppTapsExitPreferenceController.java b/src/com/android/settings/gestures/OneHandedMainSwitchPreferenceController.java index 1cc7911c02..2b7d7a0f30 100644 --- a/src/com/android/settings/gestures/OneHandedAppTapsExitPreferenceController.java +++ b/src/com/android/settings/gestures/OneHandedMainSwitchPreferenceController.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2021 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. @@ -19,62 +19,58 @@ package com.android.settings.gestures; import android.content.Context; import android.net.Uri; -import androidx.preference.Preference; import androidx.preference.PreferenceScreen; -import com.android.settings.core.TogglePreferenceController; +import com.android.settings.widget.SettingsMainSwitchPreferenceController; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnStart; import com.android.settingslib.core.lifecycle.events.OnStop; +import com.android.settingslib.widget.MainSwitchPreference; /** - * The Controller to handle app taps to exit of one-handed mode - */ -public class OneHandedAppTapsExitPreferenceController extends TogglePreferenceController implements - LifecycleObserver, OnStart, OnStop, OneHandedSettingsUtils.TogglesCallback { + * The controller to handle one-handed mode main switch enable or disable state. + **/ +public class OneHandedMainSwitchPreferenceController extends + SettingsMainSwitchPreferenceController implements OneHandedSettingsUtils.TogglesCallback, + LifecycleObserver, OnStart, OnStop { private final OneHandedSettingsUtils mUtils; - private Preference mPreference; - - public OneHandedAppTapsExitPreferenceController(Context context, String key) { - super(context, key); + private MainSwitchPreference mPreference; + public OneHandedMainSwitchPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); mUtils = new OneHandedSettingsUtils(context); - - // By default, app taps to stop one-handed is enabled, this will get default value once. - OneHandedSettingsUtils.setTapsAppToExitEnabled(mContext, isChecked()); } @Override public int getAvailabilityStatus() { - return OneHandedSettingsUtils.isOneHandedModeEnabled(mContext) + return (OneHandedSettingsUtils.isSupportOneHandedMode() + && OneHandedSettingsUtils.getNavigationBarMode(mContext) != 0 /* 3-button mode */) ? AVAILABLE : DISABLED_DEPENDENT_SETTING; } @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - mPreference = screen.findPreference(getPreferenceKey()); - } - - @Override - public void updateState(Preference preference) { - super.updateState(preference); - - final int availabilityStatus = getAvailabilityStatus(); - preference.setEnabled( - availabilityStatus == AVAILABLE || availabilityStatus == AVAILABLE_UNSEARCHABLE); + public boolean isChecked() { + return OneHandedSettingsUtils.isOneHandedModeEnabled(mContext); } @Override public boolean setChecked(boolean isChecked) { - return OneHandedSettingsUtils.setTapsAppToExitEnabled(mContext, isChecked); + if (isChecked) { + // Set default value for TapsAppToExit and Timeout + OneHandedSettingsUtils.setTapsAppToExitEnabled(mContext, true); + OneHandedSettingsUtils.setTimeoutValue(mContext, + OneHandedSettingsUtils.OneHandedTimeout.MEDIUM.getValue()); + } + OneHandedSettingsUtils.setOneHandedModeEnabled(mContext, isChecked); + return true; } @Override - public boolean isChecked() { - return OneHandedSettingsUtils.isTapsAppToExitEnabled(mContext); + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); } @Override @@ -89,6 +85,11 @@ public class OneHandedAppTapsExitPreferenceController extends TogglePreferenceCo @Override public void onChange(Uri uri) { - updateState(mPreference); + if (mPreference == null) { + return; + } + if (uri.equals(OneHandedSettingsUtils.ONE_HANDED_MODE_ENABLED_URI)) { + mPreference.setChecked(isChecked()); + } } } diff --git a/src/com/android/settings/gestures/OneHandedSettings.java b/src/com/android/settings/gestures/OneHandedSettings.java index 11fc774cd3..6d1cbfd35d 100644 --- a/src/com/android/settings/gestures/OneHandedSettings.java +++ b/src/com/android/settings/gestures/OneHandedSettings.java @@ -17,21 +17,41 @@ package com.android.settings.gestures; import android.app.settings.SettingsEnums; +import android.content.ComponentName; import android.content.Context; +import android.os.Bundle; import android.os.UserHandle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import com.android.internal.accessibility.AccessibilityShortcutController; import com.android.settings.R; -import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.accessibility.AccessibilityShortcutPreferenceFragment; import com.android.settings.search.BaseSearchIndexProvider; -import com.android.settingslib.search.SearchIndexable; /** - * The Fragment for one-handed mode settings. + * Fragment for One-handed mode settings + * + * <p>The child {@link AccessibilityShortcutPreferenceFragment} shows the actual UI for + * providing basic accessibility shortcut service setup. */ -@SearchIndexable -public class OneHandedSettings extends DashboardFragment { +public class OneHandedSettings extends AccessibilityShortcutPreferenceFragment { + private static final String ONE_HANDED_SHORTCUT_KEY = "one_handed_shortcuts_preference"; + private String mFeatureName; - private static final String TAG = "OneHandedSettings"; + @Override + protected void updatePreferenceStates() { + OneHandedSettingsUtils.setUserId(UserHandle.myUserId()); + super.updatePreferenceStates(); + } + + @Override + public int getDialogMetricsCategory(int dialogId) { + final int dialogMetrics = super.getDialogMetricsCategory(dialogId); + return dialogMetrics == SettingsEnums.ACTION_UNKNOWN ? SettingsEnums.SETTINGS_ONE_HANDED + : dialogMetrics; + } @Override public int getMetricsCategory() { @@ -39,14 +59,29 @@ public class OneHandedSettings extends DashboardFragment { } @Override - protected String getLogTag() { - return TAG; + protected String getShortcutPreferenceKey() { + return ONE_HANDED_SHORTCUT_KEY; } @Override - protected void updatePreferenceStates() { - OneHandedSettingsUtils.setUserId(UserHandle.myUserId()); - super.updatePreferenceStates(); + protected boolean showGeneralCategory() { + return true; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return super.onCreateView(inflater, container, savedInstanceState); + } + + @Override + protected ComponentName getComponentName() { + return AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME; + } + + @Override + protected CharSequence getLabelName() { + return mFeatureName; } @Override @@ -54,6 +89,17 @@ public class OneHandedSettings extends DashboardFragment { return R.xml.one_handed_settings; } + @Override + protected String getLogTag() { + return null; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + mFeatureName = getContext().getString(R.string.one_handed_title); + super.onCreate(savedInstanceState); + } + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider(R.xml.one_handed_settings) { @Override diff --git a/src/com/android/settings/gestures/OneHandedSettingsUtils.java b/src/com/android/settings/gestures/OneHandedSettingsUtils.java index 21998a6e21..a9311296b4 100644 --- a/src/com/android/settings/gestures/OneHandedSettingsUtils.java +++ b/src/com/android/settings/gestures/OneHandedSettingsUtils.java @@ -26,6 +26,8 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings; +import androidx.annotation.VisibleForTesting; + /** * The Util to query one-handed mode settings config */ @@ -34,6 +36,10 @@ public class OneHandedSettingsUtils { static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode"; static final int OFF = 0; static final int ON = 1; + static final Uri ONE_HANDED_MODE_ENABLED_URI = + Settings.Secure.getUriFor(Settings.Secure.ONE_HANDED_MODE_ENABLED); + static final Uri SHOW_NOTIFICATION_ENABLED_URI = + Settings.Secure.getUriFor(Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED); public enum OneHandedTimeout { NEVER(0), SHORT(4), MEDIUM(8), LONG(12); @@ -177,6 +183,21 @@ public class OneHandedSettingsUtils { } /** + * Set NavigationBar mode flag to Settings provider. + * @param context App context + * @param value Navigation bar mode: + * 0 = 3 button + * 1 = 2 button + * 2 = fully gestural + * @return true if the value was set, false on database errors. + */ + @VisibleForTesting + public boolean setNavigationBarMode(Context context, String value) { + return Settings.Secure.putStringForUser(context.getContentResolver(), + Settings.Secure.NAVIGATION_MODE, value, UserHandle.myUserId()); + } + + /** * Get NavigationBar mode flag from Settings provider. * @param context App context * @return Navigation bar mode: @@ -190,12 +211,17 @@ public class OneHandedSettingsUtils { } /** - * + * Check if One-handed mode settings controllers can enabled or disabled. * @param context App context - * @return Support One-Handed mode feature or not. + * @return true if controllers are able to enabled, false otherwise. + * + * Note: For better UX experience, just disabled controls that let users know to use + * this feature, they need to make sure gesture navigation is turned on in system + * navigation settings. */ - public static boolean isFeatureAvailable(Context context) { - return isSupportOneHandedMode() && getNavigationBarMode(context) != 0; + public static boolean canEnableController(Context context) { + return (OneHandedSettingsUtils.isOneHandedModeEnabled(context) + && OneHandedSettingsUtils.getNavigationBarMode(context) != 0 /* 3-button mode */); } /** @@ -218,9 +244,6 @@ public class OneHandedSettingsUtils { private final class SettingsObserver extends ContentObserver { private TogglesCallback mCallback; - private final Uri mOneHandedEnabledAware = Settings.Secure.getUriFor( - Settings.Secure.ONE_HANDED_MODE_ENABLED); - SettingsObserver(Handler handler) { super(handler); } @@ -231,7 +254,8 @@ public class OneHandedSettingsUtils { public void observe() { final ContentResolver resolver = mContext.getContentResolver(); - resolver.registerContentObserver(mOneHandedEnabledAware, true, this); + resolver.registerContentObserver(ONE_HANDED_MODE_ENABLED_URI, true, this); + resolver.registerContentObserver(SHOW_NOTIFICATION_ENABLED_URI, true, this); } @Override diff --git a/src/com/android/settings/gestures/OneHandedTimeoutPreferenceController.java b/src/com/android/settings/gestures/OneHandedTimeoutPreferenceController.java deleted file mode 100644 index 8ce0e86ffd..0000000000 --- a/src/com/android/settings/gestures/OneHandedTimeoutPreferenceController.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (C) 2020 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.gestures; - -import android.content.Context; -import android.net.Uri; - -import androidx.preference.ListPreference; -import androidx.preference.Preference; -import androidx.preference.PreferenceScreen; - -import com.android.settings.R; -import com.android.settings.core.BasePreferenceController; -import com.android.settingslib.core.lifecycle.LifecycleObserver; -import com.android.settingslib.core.lifecycle.events.OnStart; -import com.android.settingslib.core.lifecycle.events.OnStop; - -import java.util.HashMap; -import java.util.Map; - -/** - * The Controller to handle one-handed mode timeout state. - **/ -public class OneHandedTimeoutPreferenceController extends BasePreferenceController implements - Preference.OnPreferenceChangeListener, LifecycleObserver, OnStart, OnStop, - OneHandedSettingsUtils.TogglesCallback { - - private final Map<String, String> mTimeoutMap; - private final OneHandedSettingsUtils mUtils; - - private Preference mTimeoutPreference; - - public OneHandedTimeoutPreferenceController(Context context, String preferenceKey) { - super(context, preferenceKey); - - mTimeoutMap = new HashMap<>(); - initTimeoutMap(); - mUtils = new OneHandedSettingsUtils(context); - } - - @Override - public int getAvailabilityStatus() { - return OneHandedSettingsUtils.isOneHandedModeEnabled(mContext) - ? AVAILABLE : DISABLED_DEPENDENT_SETTING; - } - - @Override - public boolean onPreferenceChange(Preference preference, Object object) { - if (!(preference instanceof ListPreference)) { - return false; - } - final int newValue = Integer.parseInt((String) object); - OneHandedSettingsUtils.setTimeoutValue(mContext, newValue); - updateState(preference); - return true; - } - - @Override - public void updateState(Preference preference) { - super.updateState(preference); - if (!(preference instanceof ListPreference)) { - return; - } - final ListPreference listPreference = (ListPreference) preference; - listPreference.setValue(getTimeoutValue()); - - final int availabilityStatus = getAvailabilityStatus(); - preference.setEnabled( - availabilityStatus == AVAILABLE || availabilityStatus == AVAILABLE_UNSEARCHABLE); - } - - @Override - public CharSequence getSummary() { - if (OneHandedSettingsUtils.getTimeoutValue(mContext) == 0) { - return mContext.getResources().getString(R.string.screensaver_settings_summary_never); - } - return String.format(mContext.getResources().getString( - R.string.screen_timeout_summary), mTimeoutMap.get(getTimeoutValue())); - } - - @Override - public void displayPreference(PreferenceScreen screen) { - super.displayPreference(screen); - mTimeoutPreference = screen.findPreference(mPreferenceKey); - } - - @Override - public void onStart() { - mUtils.registerToggleAwareObserver(this); - } - - @Override - public void onStop() { - mUtils.unregisterToggleAwareObserver(); - } - - @Override - public void onChange(Uri uri) { - updateState(mTimeoutPreference); - } - - private String getTimeoutValue() { - return String.valueOf(OneHandedSettingsUtils.getTimeoutValue(mContext)); - } - - private void initTimeoutMap() { - if (mTimeoutMap.size() != 0) { - return; - } - - final String[] timeoutValues = mContext.getResources().getStringArray( - R.array.one_handed_timeout_values); - final String[] timeoutTitles = mContext.getResources().getStringArray( - R.array.one_handed_timeout_title); - - if (timeoutValues.length != timeoutTitles.length) { - return; - } - - for (int i = 0; i < timeoutValues.length; i++) { - mTimeoutMap.put(timeoutValues[i], timeoutTitles[i]); - } - } -} diff --git a/src/com/android/settings/gestures/PreventRingingGesturePreferenceController.java b/src/com/android/settings/gestures/PreventRingingGesturePreferenceController.java index 541bfe4576..2f89c16131 100644 --- a/src/com/android/settings/gestures/PreventRingingGesturePreferenceController.java +++ b/src/com/android/settings/gestures/PreventRingingGesturePreferenceController.java @@ -30,7 +30,6 @@ import androidx.preference.PreferenceScreen; import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; -import com.android.settings.widget.VideoPreference; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.core.lifecycle.LifecycleObserver; @@ -52,8 +51,6 @@ public class PreventRingingGesturePreferenceController extends AbstractPreferenc private final String KEY = "gesture_prevent_ringing_category"; private final Context mContext; - private VideoPreference mVideoPreference; - @VisibleForTesting PreferenceCategory mPreferenceCategory; @VisibleForTesting @@ -85,8 +82,6 @@ public class PreventRingingGesturePreferenceController extends AbstractPreferenc if (mPreferenceCategory != null) { mSettingObserver = new SettingObserver(mPreferenceCategory); } - - mVideoPreference = screen.findPreference(getVideoPrefKey()); } @Override @@ -142,10 +137,6 @@ public class PreventRingingGesturePreferenceController extends AbstractPreferenc mSettingObserver.register(mContext.getContentResolver()); mSettingObserver.onChange(false, null); } - - if (mVideoPreference != null) { - mVideoPreference.onViewVisible(); - } } @Override @@ -153,10 +144,6 @@ public class PreventRingingGesturePreferenceController extends AbstractPreferenc if (mSettingObserver != null) { mSettingObserver.unregister(mContext.getContentResolver()); } - - if (mVideoPreference != null) { - mVideoPreference.onViewInvisible(); - } } private int keyToSetting(String key) { diff --git a/src/com/android/settings/gestures/SwipeBottomToNotificationPreferenceController.java b/src/com/android/settings/gestures/SwipeBottomToNotificationPreferenceController.java deleted file mode 100644 index ec81482dd6..0000000000 --- a/src/com/android/settings/gestures/SwipeBottomToNotificationPreferenceController.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2020 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.gestures; - - -import android.content.Context; - -import com.android.settings.R; -import com.android.settings.core.TogglePreferenceController; - -/** - * Handles swipe bottom to expand notification panel gesture. - **/ -public class SwipeBottomToNotificationPreferenceController extends TogglePreferenceController { - - public SwipeBottomToNotificationPreferenceController(Context context, String key) { - super(context, key); - } - - @Override - public int getAvailabilityStatus() { - return OneHandedSettingsUtils.isFeatureAvailable(mContext) - ? AVAILABLE : UNSUPPORTED_ON_DEVICE; - } - - @Override - public boolean isSliceable() { - return true; - } - - @Override - public boolean isPublicSlice() { - return true; - } - - @Override - public boolean setChecked(boolean isChecked) { - if (isChecked) { - OneHandedSettingsUtils.setOneHandedModeEnabled(mContext, false); - } - OneHandedSettingsUtils.setSwipeDownNotificationEnabled(mContext, isChecked); - return true; - } - - @Override - public boolean isChecked() { - return OneHandedSettingsUtils.isSwipeDownNotificationEnabled(mContext); - } - - @Override - public CharSequence getSummary() { - // This toggle preference summary will be updated in gesture preference page since we bound - // it with entry preference in gesture.xml - return mContext.getText( - isChecked() ? R.string.gesture_setting_on : R.string.gesture_setting_off); - } -} diff --git a/src/com/android/settings/gestures/SwipeBottomToNotificationSettings.java b/src/com/android/settings/gestures/SwipeBottomToNotificationSettings.java deleted file mode 100644 index 9d85f1123a..0000000000 --- a/src/com/android/settings/gestures/SwipeBottomToNotificationSettings.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2020 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.gestures; - -import android.app.settings.SettingsEnums; -import android.content.Context; - -import com.android.settings.R; -import com.android.settings.dashboard.DashboardFragment; -import com.android.settings.search.BaseSearchIndexProvider; - -/** - * The Fragment for swipe bottom to notification gesture settings. - */ -public class SwipeBottomToNotificationSettings extends DashboardFragment { - - private static final String TAG = "SwipeBottomToNotificationSettings"; - - @Override - public int getMetricsCategory() { - return SettingsEnums.SETTINGS_SWIPE_BOTTOM_TO_NOTIFICATION; - } - - @Override - protected String getLogTag() { - return TAG; - } - - @Override - protected int getPreferenceScreenResId() { - return R.xml.swipe_bottom_to_notification_settings; - } - - public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = - new BaseSearchIndexProvider(R.xml.swipe_bottom_to_notification_settings) { - - @Override - protected boolean isPageSearchEnabled(Context context) { - if (!OneHandedSettingsUtils.isSupportOneHandedMode()) { - return false; - } - return !OneHandedSettingsUtils.isOneHandedModeEnabled(context); - } - }; -} diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index 5950e4b8ba..1d7b5dc14b 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -18,14 +18,11 @@ package com.android.settings.homepage; import android.animation.LayoutTransition; import android.app.ActivityManager; -import android.app.ActivityOptions; import android.app.settings.SettingsEnums; -import android.content.Intent; import android.os.Bundle; import android.util.FeatureFlagUtils; import android.util.Log; import android.view.View; -import android.view.Window; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.Toolbar; @@ -36,15 +33,16 @@ import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; import com.android.settings.R; -import com.android.settings.Utils; import com.android.settings.accounts.AvatarViewMixin; +import com.android.settings.core.CategoryMixin; import com.android.settings.core.FeatureFlags; import com.android.settings.homepage.contextualcards.ContextualCardsFragment; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin; -import com.android.settingslib.transition.SettingsTransitionHelper; -public class SettingsHomepageActivity extends FragmentActivity { +/** Settings homepage activity */ +public class SettingsHomepageActivity extends FragmentActivity implements + CategoryMixin.CategoryHandler { private static final String TAG = "SettingsHomepageActivity"; @@ -52,6 +50,12 @@ public class SettingsHomepageActivity extends FragmentActivity { private View mHomepageView; private View mSuggestionView; + private CategoryMixin mCategoryMixin; + + @Override + public CategoryMixin getCategoryMixin() { + return mCategoryMixin; + } /** * Shows the homepage and shows/hides the suggestion together. Only allows to be executed once @@ -69,12 +73,6 @@ public class SettingsHomepageActivity extends FragmentActivity { @Override protected void onCreate(Bundle savedInstanceState) { - if (Utils.isPageTransitionEnabled(this)) { - // Enable Activity transitions - getWindow().requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS); - SettingsTransitionHelper.applyForwardTransition(this); - SettingsTransitionHelper.applyBackwardTransition(this); - } super.onCreate(savedInstanceState); setContentView(R.layout.settings_homepage_container); @@ -87,6 +85,8 @@ public class SettingsHomepageActivity extends FragmentActivity { .initSearchToolbar(this /* activity */, toolbar, SettingsEnums.SETTINGS_HOMEPAGE); getLifecycle().addObserver(new HideNonSystemOverlayMixin(this)); + mCategoryMixin = new CategoryMixin(this); + getLifecycle().addObserver(mCategoryMixin); if (!getSystemService(ActivityManager.class).isLowRamDevice()) { // Only allow features on high ram devices. @@ -107,16 +107,6 @@ public class SettingsHomepageActivity extends FragmentActivity { .getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); } - @Override - public void startActivity(Intent intent) { - if (Utils.isPageTransitionEnabled(this)) { - final Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(this).toBundle(); - super.startActivity(intent, bundle); - return; - } - super.startActivity(intent); - } - private void showSuggestionFragment() { final Class<? extends Fragment> fragment = FeatureFactory.getFactory(this) .getSuggestionFeatureProvider(this).getContextualSuggestionFragment(); diff --git a/src/com/android/settings/media/MediaDeviceUpdateWorker.java b/src/com/android/settings/media/MediaDeviceUpdateWorker.java index 14e82674c3..dd27073d49 100644 --- a/src/com/android/settings/media/MediaDeviceUpdateWorker.java +++ b/src/com/android/settings/media/MediaDeviceUpdateWorker.java @@ -259,6 +259,10 @@ public class MediaDeviceUpdateWorker extends SliceBackgroundWorker return mLocalMediaManager.shouldDisableMediaOutput(packageName); } + boolean shouldEnableVolumeSeekBar(RoutingSessionInfo sessionInfo) { + return mLocalMediaManager.shouldEnableVolumeSeekBar(sessionInfo); + } + private class DevicesChangedBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { diff --git a/src/com/android/settings/media/RemoteMediaSlice.java b/src/com/android/settings/media/RemoteMediaSlice.java index e69c005014..3d81c44b33 100644 --- a/src/com/android/settings/media/RemoteMediaSlice.java +++ b/src/com/android/settings/media/RemoteMediaSlice.java @@ -126,6 +126,12 @@ public class RemoteMediaSlice implements CustomSliceable { + maxVolume); continue; } + if (!getWorker().shouldEnableVolumeSeekBar(info)) { + // There is no disable state. We hide it directly. + Log.d(TAG, "Unable to add Slice. " + info.getName() + ": This is a group session"); + continue; + } + final CharSequence appName = Utils.getApplicationLabel( mContext, info.getClientPackageName()); final CharSequence outputTitle = mContext.getString(R.string.media_output_label_title, diff --git a/src/com/android/settings/network/CarrierWifiTogglePreferenceController.java b/src/com/android/settings/network/CarrierWifiTogglePreferenceController.java index cf65034d9c..c5d8b97309 100644 --- a/src/com/android/settings/network/CarrierWifiTogglePreferenceController.java +++ b/src/com/android/settings/network/CarrierWifiTogglePreferenceController.java @@ -78,7 +78,6 @@ public class CarrierWifiTogglePreferenceController extends TogglePreferenceContr return false; } mWifiPickerTrackerHelper.setCarrierNetworkEnabled(isChecked); - updateCarrierNetworkPreference(isChecked); return true; } @@ -86,21 +85,17 @@ public class CarrierWifiTogglePreferenceController extends TogglePreferenceContr public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); mCarrierNetworkPreference = screen.findPreference(CARRIER_WIFI_NETWORK_PREF_KEY); - updateCarrierNetworkPreference(isChecked()); + updateCarrierNetworkPreference(); } @Override public void onWifiStateChanged() { - if (mCarrierNetworkPreference != null && mCarrierNetworkPreference.isVisible()) { - mCarrierNetworkPreference.setSummary(getCarrierNetworkSsid()); - } + updateCarrierNetworkPreference(); } @Override public void onWifiEntriesChanged() { - if (mCarrierNetworkPreference != null && mCarrierNetworkPreference.isVisible()) { - mCarrierNetworkPreference.setSummary(getCarrierNetworkSsid()); - } + updateCarrierNetworkPreference(); } @Override @@ -113,11 +108,11 @@ public class CarrierWifiTogglePreferenceController extends TogglePreferenceContr // Do nothing } - protected void updateCarrierNetworkPreference(boolean isCarrierNetworkEnabled) { + protected void updateCarrierNetworkPreference() { if (mCarrierNetworkPreference == null) { return; } - if (!isCarrierNetworkEnabled || getAvailabilityStatus() != AVAILABLE) { + if (getAvailabilityStatus() != AVAILABLE || !isCarrierNetworkActive()) { mCarrierNetworkPreference.setVisible(false); return; } @@ -125,6 +120,13 @@ public class CarrierWifiTogglePreferenceController extends TogglePreferenceContr mCarrierNetworkPreference.setSummary(getCarrierNetworkSsid()); } + protected boolean isCarrierNetworkActive() { + if (mWifiPickerTrackerHelper == null) { + return false; + } + return mWifiPickerTrackerHelper.isCarrierNetworkActive(); + } + protected String getCarrierNetworkSsid() { if (mWifiPickerTrackerHelper == null) { return null; diff --git a/src/com/android/settings/network/NetworkDashboardFragment.java b/src/com/android/settings/network/NetworkDashboardFragment.java index f023ced6ef..c53b4f652a 100644 --- a/src/com/android/settings/network/NetworkDashboardFragment.java +++ b/src/com/android/settings/network/NetworkDashboardFragment.java @@ -95,11 +95,6 @@ public class NetworkDashboardFragment extends DashboardFragment implements this /* fragment */, this /* mobilePlanHost */); } - @Override - protected boolean isParalleledControllers() { - return true; - } - private static List<AbstractPreferenceController> buildPreferenceControllers(Context context, Lifecycle lifecycle, MetricsFeatureProvider metricsFeatureProvider, Fragment fragment, MobilePlanPreferenceHost mobilePlanHost) { diff --git a/src/com/android/settings/network/NetworkProviderSettings.java b/src/com/android/settings/network/NetworkProviderSettings.java index 22b9bccc52..25cf4ce7da 100644 --- a/src/com/android/settings/network/NetworkProviderSettings.java +++ b/src/com/android/settings/network/NetworkProviderSettings.java @@ -169,9 +169,18 @@ public class NetworkProviderSettings extends RestrictedSettingsFragment return WifiPickerTracker.isVerboseLoggingEnabled(); } + private boolean mIsViewLoading; + private final Runnable mRemoveLoadingRunnable = () -> { + if (mIsViewLoading) { + setLoading(false, false); + mIsViewLoading = false; + } + }; + private boolean mIsWifiEntryListStale = true; private final Runnable mUpdateWifiEntryPreferencesRunnable = () -> { updateWifiEntryPreferences(); + getView().postDelayed(mRemoveLoadingRunnable, 10); }; private final Runnable mHideProgressBarRunnable = () -> { setProgressBarVisible(false); @@ -245,11 +254,21 @@ public class NetworkProviderSettings extends RestrictedSettingsFragment @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - final Activity activity = getActivity(); - if (activity != null) { - mProgressHeader = setPinnedHeaderView(R.layout.progress_header) - .findViewById(R.id.progress_bar_animation); - setProgressBarVisible(false); + Activity activity = getActivity(); + if (activity == null) { + return; + } + + mProgressHeader = setPinnedHeaderView(R.layout.progress_header) + .findViewById(R.id.progress_bar_animation); + setProgressBarVisible(false); + + mWifiManager = activity.getSystemService(WifiManager.class); + if (mWifiManager != null) { + setLoading(true, false); + mIsViewLoading = true; + getView().postDelayed(mRemoveLoadingRunnable, + mWifiManager.isWifiEnabled() ? 1000 : 100); } } @@ -332,12 +351,6 @@ public class NetworkProviderSettings extends RestrictedSettingsFragment mWifiPickerTracker = mWifiPickerTrackerHelper.getWifiPickerTracker(); mInternetUpdater = new InternetUpdater(getContext(), getSettingsLifecycle(), this); - final Activity activity = getActivity(); - - if (activity != null) { - mWifiManager = getActivity().getSystemService(WifiManager.class); - } - mConnectListener = new WifiConnectListener(getActivity()); mSaveListener = new WifiManager.ActionListener() { diff --git a/src/com/android/settings/network/ProviderModelSlice.java b/src/com/android/settings/network/ProviderModelSlice.java index cfaef53ac6..aafe715c4a 100644 --- a/src/com/android/settings/network/ProviderModelSlice.java +++ b/src/com/android/settings/network/ProviderModelSlice.java @@ -22,16 +22,20 @@ import static android.app.slice.Slice.EXTRA_TOGGLE_STATE; import static com.android.settings.slices.CustomSliceRegistry.PROVIDER_MODEL_SLICE_URI; import android.annotation.ColorInt; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; import android.app.PendingIntent; import android.app.settings.SettingsEnums; import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.telephony.SubscriptionManager; import android.util.Log; +import android.view.WindowManager.LayoutParams; import androidx.annotation.VisibleForTesting; import androidx.core.graphics.drawable.IconCompat; @@ -64,13 +68,16 @@ import java.util.stream.Collectors; public class ProviderModelSlice extends WifiSlice { private static final String TAG = "ProviderModelSlice"; - protected static final String ACTION_TITLE_CONNECT_TO_CARRIER = "Connect_To_Carrier"; + protected static final String PREF_NAME = "ProviderModelSlice"; + protected static final String PREF_HAS_TURNED_OFF_MOBILE_DATA = "PrefHasTurnedOffMobileData"; private final ProviderModelSliceHelper mHelper; + private final SharedPreferences mSharedPref; public ProviderModelSlice(Context context) { super(context); mHelper = getHelper(); + mSharedPref = getSharedPreference(); } @Override @@ -195,10 +202,21 @@ public class ProviderModelSlice extends WifiSlice { boolean isToggleAction = intent.hasExtra(EXTRA_TOGGLE_STATE); boolean newState = intent.getBooleanExtra(EXTRA_TOGGLE_STATE, mHelper.isMobileDataEnabled()); + if (isToggleAction) { // The ToggleAction is used to set mobile data enabled. - MobileNetworkUtils.setMobileDataEnabled(mContext, defaultSubId, newState, - false /* disableOtherSubscriptions */); + if (!newState && mSharedPref != null + && mSharedPref.getBoolean(PREF_HAS_TURNED_OFF_MOBILE_DATA, true)) { + String carrierName = mHelper.getMobileTitle(); + if (carrierName.equals(mContext.getString(R.string.mobile_data_settings_title))) { + carrierName = mContext.getString( + R.string.mobile_data_disable_message_default_carrier); + } + showMobileDataDisableDialog(getMobileDataDisableDialog(defaultSubId, carrierName)); + } else { + MobileNetworkUtils.setMobileDataEnabled(mContext, defaultSubId, newState, + false /* disableOtherSubscriptions */); + } } final boolean isDataEnabled = @@ -207,6 +225,38 @@ public class ProviderModelSlice extends WifiSlice { } @VisibleForTesting + AlertDialog getMobileDataDisableDialog(int defaultSubId, String carrierName) { + return new Builder(mContext) + .setTitle(R.string.mobile_data_disable_title) + .setMessage(mContext.getString(R.string.mobile_data_disable_message, + carrierName)) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton( + com.android.internal.R.string.alert_windows_notification_turn_off_action, + (dialog, which) -> { + MobileNetworkUtils.setMobileDataEnabled(mContext, defaultSubId, + false /* enabled */, + false /* disableOtherSubscriptions */); + if (mSharedPref != null) { + SharedPreferences.Editor editor = mSharedPref.edit(); + editor.putBoolean(PREF_HAS_TURNED_OFF_MOBILE_DATA, false); + editor.apply(); + } + }) + .create(); + } + + private void showMobileDataDisableDialog(AlertDialog dialog) { + if (dialog == null) { + log("AlertDialog is null"); + return; + } + + dialog.getWindow().setType(LayoutParams.TYPE_KEYGUARD_DIALOG); + dialog.show(); + } + + @VisibleForTesting void doCarrierNetworkAction(boolean isToggleAction, boolean isDataEnabled, int subId) { final NetworkProviderWorker worker = getWorker(); if (worker == null) { @@ -248,6 +298,11 @@ public class ProviderModelSlice extends WifiSlice { return SliceBackgroundWorker.getInstance(getUri()); } + @VisibleForTesting + SharedPreferences getSharedPreference() { + return mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); + } + private @InternetUpdater.InternetType int getInternetType() { final NetworkProviderWorker worker = getWorker(); if (worker == null) { @@ -295,31 +350,6 @@ public class ProviderModelSlice extends WifiSlice { } @Override - protected ListBuilder.RowBuilder getWifiSliceItemRow(WifiSliceItem wifiSliceItem) { - final CharSequence title = wifiSliceItem.getTitle(); - final IconCompat levelIcon = getWifiSliceItemLevelIcon(wifiSliceItem); - final ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder() - .setTitleItem(levelIcon, ListBuilder.ICON_IMAGE) - .setTitle(title) - .setSubtitle(wifiSliceItem.getSummary()) - .setContentDescription(wifiSliceItem.getContentDescription()); - - final IconCompat endIcon; - if (wifiSliceItem.hasInternetAccess()) { - rowBuilder.setPrimaryAction(SliceAction.create(getBroadcastIntent(mContext), - levelIcon, ListBuilder.ICON_IMAGE, ACTION_TITLE_CONNECT_TO_CARRIER)); - endIcon = IconCompat.createWithResource(mContext, R.drawable.ic_settings_close); - } else { - rowBuilder.setPrimaryAction(getWifiEntryAction(wifiSliceItem, levelIcon, title)); - endIcon = getEndIcon(wifiSliceItem); - } - if (endIcon != null) { - rowBuilder.addEndItem(endIcon, ListBuilder.ICON_IMAGE); - } - return rowBuilder; - } - - @Override protected IconCompat getWifiSliceItemLevelIcon(WifiSliceItem wifiSliceItem) { if (wifiSliceItem.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED && getInternetType() != InternetUpdater.INTERNET_WIFI) { diff --git a/src/com/android/settings/network/ProviderModelSliceHelper.java b/src/com/android/settings/network/ProviderModelSliceHelper.java index 28f857464a..16d5c92455 100644 --- a/src/com/android/settings/network/ProviderModelSliceHelper.java +++ b/src/com/android/settings/network/ProviderModelSliceHelper.java @@ -264,7 +264,7 @@ public class ProviderModelSliceHelper { return summary; } - private String getMobileTitle() { + protected String getMobileTitle() { String title = mContext.getText(R.string.mobile_data_settings_title).toString(); if (mSubscriptionManager == null) { return title; diff --git a/src/com/android/settings/network/SubscriptionUtil.java b/src/com/android/settings/network/SubscriptionUtil.java index e7dd5e914c..48ff591a43 100644 --- a/src/com/android/settings/network/SubscriptionUtil.java +++ b/src/com/android/settings/network/SubscriptionUtil.java @@ -54,6 +54,7 @@ import java.util.stream.Stream; public class SubscriptionUtil { private static final String TAG = "SubscriptionUtil"; + private static final String PROFILE_GENERIC_DISPLAY_NAME = "CARD"; private static List<SubscriptionInfo> sAvailableResultsForTesting; private static List<SubscriptionInfo> sActiveResultsForTesting; @@ -257,7 +258,10 @@ public class SubscriptionUtil { .map(i -> { DisplayInfo info = new DisplayInfo(); info.subscriptionInfo = i; - info.originalName = i.getDisplayName().toString().trim(); + String displayName = i.getDisplayName().toString(); + info.originalName = TextUtils.equals(displayName, PROFILE_GENERIC_DISPLAY_NAME) + ? context.getResources().getString(R.string.sim_card) + : displayName.trim(); return info; }); diff --git a/src/com/android/settings/network/SubscriptionsPreferenceController.java b/src/com/android/settings/network/SubscriptionsPreferenceController.java index 8c7347bfb8..e88cded175 100644 --- a/src/com/android/settings/network/SubscriptionsPreferenceController.java +++ b/src/com/android/settings/network/SubscriptionsPreferenceController.java @@ -277,12 +277,12 @@ public class SubscriptionsPreferenceController extends AbstractPreferenceControl final boolean isDataInService = tmForSubId.getDataState() == TelephonyManager.DATA_CONNECTED; - final boolean isActiveCarrierNetwork = + final boolean isCarrierNetworkActive = (mWifiPickerTrackerHelper != null) - && mWifiPickerTrackerHelper.isActiveCarrierNetwork(); + && mWifiPickerTrackerHelper.isCarrierNetworkActive(); String result = mSubsPrefCtrlInjector.getNetworkType( - mContext, mConfig, mTelephonyDisplayInfo, subId, isActiveCarrierNetwork); - if (mSubsPrefCtrlInjector.isActiveCellularNetwork(mContext) || isActiveCarrierNetwork) { + mContext, mConfig, mTelephonyDisplayInfo, subId, isCarrierNetworkActive); + if (mSubsPrefCtrlInjector.isActiveCellularNetwork(mContext) || isCarrierNetworkActive) { Log.i(TAG, "Active cellular network or active carrier network."); result = mContext.getString(R.string.preference_summary_default_combination, mContext.getString(R.string.mobile_data_connection_active), result); @@ -306,7 +306,7 @@ public class SubscriptionsPreferenceController extends AbstractPreferenceControl final boolean isActiveCellularNetwork = mSubsPrefCtrlInjector.isActiveCellularNetwork(mContext); if (isActiveCellularNetwork || (mWifiPickerTrackerHelper != null) - && mWifiPickerTrackerHelper.isActiveCarrierNetwork()) { + && mWifiPickerTrackerHelper.isCarrierNetworkActive()) { icon.setTint(Utils.getColorAccentDefaultColor(mContext)); return icon; } diff --git a/src/com/android/settings/network/apn/ApnSettings.java b/src/com/android/settings/network/apn/ApnSettings.java index 02d9b3d1f3..4df2e5ee38 100755 --- a/src/com/android/settings/network/apn/ApnSettings.java +++ b/src/com/android/settings/network/apn/ApnSettings.java @@ -88,6 +88,13 @@ public class ApnSettings extends RestrictedSettingsFragment Telephony.Carriers.EDITED_STATUS, }; + /** Copied from {@code com.android.internal.telephony.TelephonyIntents} */ + private static final String ACTION_SIM_STATE_CHANGED = + "android.intent.action.SIM_STATE_CHANGED"; + /** Copied from {@code com.android.internal.telephony.IccCardConstants} */ + public static final String INTENT_KEY_ICC_STATE = "ss"; + public static final String INTENT_VALUE_ICC_ABSENT = "ABSENT"; + private static final int ID_INDEX = 0; private static final int NAME_INDEX = 1; private static final int APN_INDEX = 2; @@ -151,7 +158,16 @@ public class ApnSettings extends RestrictedSettingsFragment private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (intent.getAction().equals( + String action = intent.getAction(); + if (ACTION_SIM_STATE_CHANGED.equals(action) + && intent.getStringExtra(INTENT_KEY_ICC_STATE) + .equals(INTENT_VALUE_ICC_ABSENT)) { + final SubscriptionManager sm = context.getSystemService(SubscriptionManager.class); + if (sm != null && !sm.isActiveSubscriptionId(mSubId)) { + Log.d(TAG, "Due to SIM absent, closes APN settings page"); + finish(); + } + } else if (intent.getAction().equals( TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED)) { if (mRestoreDefaultApnMode) { return; @@ -201,8 +217,9 @@ public class ApnSettings extends RestrictedSettingsFragment mSubId = activity.getIntent().getIntExtra(SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID); mPhoneId = SubscriptionUtil.getPhoneId(activity, mSubId); - mIntentFilter = new IntentFilter( - TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED); + mIntentFilter = new IntentFilter(); + mIntentFilter.addAction(TelephonyManager.ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED); + mIntentFilter.addAction(ACTION_SIM_STATE_CHANGED); setIfOnlyAvailableForAdmins(true); diff --git a/src/com/android/settings/network/telephony/NetworkProviderWorker.java b/src/com/android/settings/network/telephony/NetworkProviderWorker.java index 369218b86a..675d60fb94 100644 --- a/src/com/android/settings/network/telephony/NetworkProviderWorker.java +++ b/src/com/android/settings/network/telephony/NetworkProviderWorker.java @@ -261,7 +261,7 @@ public class NetworkProviderWorker extends WifiScanWorker implements String iconKey = getIconKey(telephonyDisplayInfo); int resId = mapIconSets(config).get(iconKey).dataContentDescription; if (mWifiPickerTrackerHelper != null - && mWifiPickerTrackerHelper.isActiveCarrierNetwork()) { + && mWifiPickerTrackerHelper.isCarrierNetworkActive()) { MobileIconGroup carrierMergedWifiIconGroup = TelephonyIcons.CARRIER_MERGED_WIFI; resId = carrierMergedWifiIconGroup.dataContentDescription; return resId != 0 diff --git a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java index 146b575442..0064e6ccfd 100644 --- a/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java +++ b/src/com/android/settings/network/telephony/ToggleSubscriptionDialogActivity.java @@ -30,14 +30,12 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; import com.android.settings.SidecarFragment; -import com.android.settings.core.SettingsBaseActivity; import com.android.settings.network.EnableMultiSimSidecar; import com.android.settings.network.SubscriptionUtil; import com.android.settings.network.SwitchToEuiccSubscriptionSidecar; import com.android.settings.network.SwitchToRemovableSlotSidecar; import com.android.settings.network.UiccSlotUtil; import com.android.settings.sim.SimActivationNotifier; -import com.android.settingslib.transition.SettingsTransitionHelper; import com.google.common.collect.ImmutableList; @@ -70,9 +68,6 @@ public class ToggleSubscriptionDialogActivity extends SubscriptionActionDialogAc Intent intent = new Intent(context, ToggleSubscriptionDialogActivity.class); intent.putExtra(ARG_SUB_ID, subId); intent.putExtra(ARG_enable, enable); - // suppress page transition as this is a dialog - intent.putExtra(SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE, - SettingsTransitionHelper.TransitionType.TRANSITION_NONE); return intent; } diff --git a/src/com/android/settings/notification/ConfigureNotificationSettings.java b/src/com/android/settings/notification/ConfigureNotificationSettings.java index 644d5cbc53..5f78acc1cc 100644 --- a/src/com/android/settings/notification/ConfigureNotificationSettings.java +++ b/src/com/android/settings/notification/ConfigureNotificationSettings.java @@ -96,11 +96,6 @@ public class ConfigureNotificationSettings extends DashboardFragment implements mNotificationAssistantPreferenceController.setBackend(new NotificationBackend()); } - @Override - protected boolean isParalleledControllers() { - return true; - } - private static List<AbstractPreferenceController> buildPreferenceControllers(Context context, Application app, Fragment host) { final List<AbstractPreferenceController> controllers = new ArrayList<>(); diff --git a/src/com/android/settings/notification/RemoteVolumeGroupController.java b/src/com/android/settings/notification/RemoteVolumeGroupController.java index dd6f516012..3d8d261aa0 100644 --- a/src/com/android/settings/notification/RemoteVolumeGroupController.java +++ b/src/com/android/settings/notification/RemoteVolumeGroupController.java @@ -129,6 +129,7 @@ public class RemoteVolumeGroupController extends BasePreferenceController implem seekBarPreference.setMin(0); seekBarPreference.setOnPreferenceChangeListener(this); seekBarPreference.setIcon(R.drawable.ic_volume_remote); + seekBarPreference.setEnabled(mLocalMediaManager.shouldEnableVolumeSeekBar(info)); mPreferenceCategory.addPreference(seekBarPreference); } diff --git a/src/com/android/settings/notification/app/AppConversationListPreferenceController.java b/src/com/android/settings/notification/app/AppConversationListPreferenceController.java index 0e89be3ddd..dd44a13f7c 100644 --- a/src/com/android/settings/notification/app/AppConversationListPreferenceController.java +++ b/src/com/android/settings/notification/app/AppConversationListPreferenceController.java @@ -33,6 +33,7 @@ import com.android.settings.R; import com.android.settings.applications.AppInfoBase; import com.android.settings.core.SubSettingLauncher; import com.android.settings.notification.NotificationBackend; +import com.android.settingslib.widget.AppPreference; import java.util.ArrayList; import java.util.Collections; @@ -144,7 +145,7 @@ public class AppConversationListPreferenceController extends NotificationPrefere } protected Preference createConversationPref(final ConversationChannelWrapper conversation) { - Preference pref = new Preference(mContext); + AppPreference pref = new AppPreference(mContext); populateConversationPreference(conversation, pref); return pref; } diff --git a/src/com/android/settings/notification/app/AppNotificationSettings.java b/src/com/android/settings/notification/app/AppNotificationSettings.java index 8d0819cd97..ebcd261df2 100644 --- a/src/com/android/settings/notification/app/AppNotificationSettings.java +++ b/src/com/android/settings/notification/app/AppNotificationSettings.java @@ -40,35 +40,11 @@ public class AppNotificationSettings extends NotificationSettings { private static final String TAG = "AppNotificationSettings"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private static String KEY_ADVANCED_CATEGORY = "app_advanced"; - private static String KEY_BADGE = "badge"; - private static String KEY_APP_LINK = "app_link"; - private static String[] LEGACY_NON_ADVANCED_KEYS = {KEY_BADGE, KEY_APP_LINK}; - @Override public int getMetricsCategory() { return SettingsEnums.NOTIFICATION_APP_NOTIFICATION; } - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - final PreferenceScreen screen = getPreferenceScreen(); - if (mShowLegacyChannelConfig && screen != null) { - // if showing legacy settings, pull advanced settings out of the advanced category - PreferenceGroup advanced = (PreferenceGroup) findPreference(KEY_ADVANCED_CATEGORY); - removePreference(KEY_ADVANCED_CATEGORY); - if (advanced != null) { - for (String key : LEGACY_NON_ADVANCED_KEYS) { - Preference pref = advanced.findPreference(key); - advanced.removePreference(pref); - if (pref != null) { - screen.addPreference(pref); - } - } - } - } - } @Override public void onResume() { @@ -80,12 +56,7 @@ public class AppNotificationSettings extends NotificationSettings { return; } - if (Utils.isPageTransitionEnabled(mContext)) { - final RecyclerView recyclerView = getListView(); - if (recyclerView != null) { - recyclerView.setItemAnimator(null); - } - } + getActivity().setTitle(mAppRow.label); for (NotificationPreferenceController controller : mControllers) { controller.onResume(mAppRow, mChannel, mChannelGroup, null, null, mSuspendedAppsAdmin, diff --git a/src/com/android/settings/notification/app/ChannelListPreferenceController.java b/src/com/android/settings/notification/app/ChannelListPreferenceController.java index b1109ecbad..12c15c28ee 100644 --- a/src/com/android/settings/notification/app/ChannelListPreferenceController.java +++ b/src/com/android/settings/notification/app/ChannelListPreferenceController.java @@ -322,7 +322,7 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr if (channel.getImportance() > IMPORTANCE_LOW) { channelPref.setIcon(getAlertingIcon()); } else { - channelPref.setIcon(null); + channelPref.setIcon(R.drawable.empty_icon); } channelPref.setIconSize(PrimarySwitchPreference.ICON_SIZE_SMALL); channelPref.setTitle(channel.getName()); @@ -350,7 +350,7 @@ public class ChannelListPreferenceController extends NotificationPreferenceContr channel.setImportance(importance); channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE); PrimarySwitchPreference channelPref1 = (PrimarySwitchPreference) preference; - channelPref1.setIcon(null); + channelPref1.setIcon(R.drawable.empty_icon); if (channel.getImportance() > IMPORTANCE_LOW) { channelPref1.setIcon(getAlertingIcon()); } diff --git a/src/com/android/settings/notification/app/ChannelNotificationSettings.java b/src/com/android/settings/notification/app/ChannelNotificationSettings.java index 296863cc0b..a7aba5284d 100644 --- a/src/com/android/settings/notification/app/ChannelNotificationSettings.java +++ b/src/com/android/settings/notification/app/ChannelNotificationSettings.java @@ -66,8 +66,9 @@ public class ChannelNotificationSettings extends NotificationSettings { return; } - if (mChannel != null && !TextUtils.isEmpty(mChannel.getConversationId()) - && !mChannel.isDemoted()) { + getActivity().setTitle(mChannel.getName()); + + if (!TextUtils.isEmpty(mChannel.getConversationId()) && !mChannel.isDemoted()) { Intent intent = new SubSettingLauncher(mContext) .setDestination(ConversationNotificationSettings.class.getName()) .setArguments(getArguments()) @@ -128,7 +129,6 @@ public class ChannelNotificationSettings extends NotificationSettings { mDependentFieldListener, mBackend)); mControllers.add(new VibrationPreferenceController(context, mBackend)); mControllers.add(new AppLinkPreferenceController(context)); - mControllers.add(new DescriptionPreferenceController(context)); mControllers.add(new VisibilityPreferenceController(context, new LockPatternUtils(context), mBackend)); mControllers.add(new LightsPreferenceController(context, mBackend)); diff --git a/src/com/android/settings/notification/app/ConversationListPreferenceController.java b/src/com/android/settings/notification/app/ConversationListPreferenceController.java index 2500a22c18..b609a9a4d5 100644 --- a/src/com/android/settings/notification/app/ConversationListPreferenceController.java +++ b/src/com/android/settings/notification/app/ConversationListPreferenceController.java @@ -18,7 +18,6 @@ package com.android.settings.notification.app; import android.app.settings.SettingsEnums; import android.content.Context; -import android.content.Intent; import android.content.pm.ShortcutInfo; import android.os.Bundle; import android.os.UserHandle; @@ -34,6 +33,7 @@ import com.android.settings.applications.AppInfoBase; import com.android.settings.core.SubSettingLauncher; import com.android.settings.notification.NotificationBackend; import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.widget.AppPreference; import java.text.Collator; import java.util.Comparator; @@ -96,7 +96,7 @@ public abstract class ConversationListPreferenceController extends AbstractPrefe protected Preference createConversationPref(final ConversationChannelWrapper conversation, int order) { - Preference pref = new Preference(mContext); + AppPreference pref = new AppPreference(mContext); pref.setOrder(order); pref.setTitle(getTitle(conversation)); diff --git a/src/com/android/settings/notification/app/ConversationNotificationSettings.java b/src/com/android/settings/notification/app/ConversationNotificationSettings.java index d712c84ca6..d659c545cd 100644 --- a/src/com/android/settings/notification/app/ConversationNotificationSettings.java +++ b/src/com/android/settings/notification/app/ConversationNotificationSettings.java @@ -41,11 +41,13 @@ public class ConversationNotificationSettings extends NotificationSettings { @Override public void onResume() { super.onResume(); - if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null || mChannel == null) { + if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null || mChannel == null + || mConversationInfo == null) { Log.w(TAG, "Missing package or uid or packageinfo or channel"); finish(); return; } + getActivity().setTitle(mConversationInfo.getLabel()); for (NotificationPreferenceController controller : mControllers) { controller.onResume(mAppRow, mChannel, mChannelGroup, mConversationDrawable, diff --git a/src/com/android/settings/notification/app/HeaderPreferenceController.java b/src/com/android/settings/notification/app/HeaderPreferenceController.java index 974ac79be0..7379d55395 100644 --- a/src/com/android/settings/notification/app/HeaderPreferenceController.java +++ b/src/com/android/settings/notification/app/HeaderPreferenceController.java @@ -84,6 +84,7 @@ public class HeaderPreferenceController extends NotificationPreferenceController pref = mHeaderController.setIcon(mAppRow.icon) .setLabel(getLabel()) .setSummary(getSummary()) + .setSecondSummary(getSecondSummary()) .setPackageName(mAppRow.pkg) .setUid(mAppRow.uid) .setButtonActions(EntityHeaderController.ActionType.ACTION_NOTIF_PREFERENCE, @@ -96,14 +97,27 @@ public class HeaderPreferenceController extends NotificationPreferenceController } } + public CharSequence getLabel() { + if (mChannel != null && !isDefaultChannel()) { + return mChannel.getName(); + } else { + return mAppRow.label; + } + } + + @OnLifecycleEvent(Lifecycle.Event.ON_START) + public void onStart() { + mStarted = true; + } + @Override public CharSequence getSummary() { - if (mChannel != null && !isDefaultChannel()) { + if (mChannel != null) { if (mChannelGroup != null && !TextUtils.isEmpty(mChannelGroup.getName())) { final SpannableStringBuilder summary = new SpannableStringBuilder(); BidiFormatter bidi = BidiFormatter.getInstance(); - summary.append(bidi.unicodeWrap(mAppRow.label.toString())); + summary.append(bidi.unicodeWrap(mAppRow.label)); summary.append(bidi.unicodeWrap(mContext.getText( R.string.notification_header_divider_symbol_with_spaces))); summary.append(bidi.unicodeWrap(mChannelGroup.getName().toString())); @@ -111,23 +125,11 @@ public class HeaderPreferenceController extends NotificationPreferenceController } else { return mAppRow.label.toString(); } - } else if (mChannelGroup != null) { - return mAppRow.label.toString(); - } else { - return ""; } + return ""; } - @OnLifecycleEvent(Lifecycle.Event.ON_START) - public void onStart() { - mStarted = true; - } - - @VisibleForTesting - CharSequence getLabel() { - return (mChannel != null && !isDefaultChannel()) ? mChannel.getName() - : mChannelGroup != null - ? mChannelGroup.getName() - : mAppRow.label; + public CharSequence getSecondSummary() { + return mChannel == null ? null : mChannel.getDescription(); } } diff --git a/src/com/android/settings/notification/app/RecentConversationsPreferenceController.java b/src/com/android/settings/notification/app/RecentConversationsPreferenceController.java index d715ce457a..4984fad940 100644 --- a/src/com/android/settings/notification/app/RecentConversationsPreferenceController.java +++ b/src/com/android/settings/notification/app/RecentConversationsPreferenceController.java @@ -167,6 +167,8 @@ public class RecentConversationsPreferenceController extends AbstractPreferenceC pref.setOnClearClickListener(() -> { try { mPs.removeRecentConversation(pkg, UserHandle.getUserId(uid), conversationId); + pref.getClearView().announceForAccessibility( + mContext.getString(R.string.recent_convo_removed)); parent.removePreference(pref); } catch (RemoteException e) { Slog.w(TAG, "Could not clear recent", e); diff --git a/src/com/android/settings/notification/zen/AbstractZenModeAutomaticRulePreferenceController.java b/src/com/android/settings/notification/zen/AbstractZenModeAutomaticRulePreferenceController.java index f6f183995e..701abbb0b6 100644 --- a/src/com/android/settings/notification/zen/AbstractZenModeAutomaticRulePreferenceController.java +++ b/src/com/android/settings/notification/zen/AbstractZenModeAutomaticRulePreferenceController.java @@ -26,8 +26,11 @@ import android.content.pm.ActivityInfo; import android.content.pm.ComponentInfo; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; +import android.os.Binder; import android.provider.Settings; import android.service.notification.ConditionProviderService; +import android.util.Log; +import android.util.Slog; import androidx.fragment.app.Fragment; import androidx.preference.Preference; @@ -36,6 +39,7 @@ import com.android.settings.core.PreferenceControllerMixin; import com.android.settingslib.core.lifecycle.Lifecycle; import java.util.Map; +import java.util.Objects; abstract public class AbstractZenModeAutomaticRulePreferenceController extends AbstractZenModePreferenceController implements PreferenceControllerMixin { @@ -92,7 +96,7 @@ abstract public class AbstractZenModeAutomaticRulePreferenceController extends ? ci.metaData.getString(ConditionProviderService.META_DATA_RULE_TYPE) : ci.metaData.getString(NotificationManager.META_DATA_AUTOMATIC_RULE_TYPE); - final ComponentName configurationActivity = getSettingsActivity(null, ci); + final ComponentName configurationActivity = getSettingsActivity(pm, null, ci); if (ruleType != null && !ruleType.trim().isEmpty() && configurationActivity != null) { final ZenRuleInfo ri = new ZenRuleInfo(); ri.serviceComponent = @@ -110,28 +114,44 @@ abstract public class AbstractZenModeAutomaticRulePreferenceController extends return null; } - protected static ComponentName getSettingsActivity(AutomaticZenRule rule, ComponentInfo ci) { + protected static ComponentName getSettingsActivity(PackageManager pm, AutomaticZenRule rule, + ComponentInfo ci) { + String owner = rule != null ? rule.getPackageName() : ci.packageName; + ComponentName settingsActivity = null; // prefer config activity on the rule itself; fallback to manifest definition if (rule != null && rule.getConfigurationActivity() != null) { - return rule.getConfigurationActivity(); - } - if (ci == null) { - return null; + settingsActivity = rule.getConfigurationActivity(); + } else { + if (ci == null) { + settingsActivity = null; + } else if (ci instanceof ActivityInfo) { + // new activity backed rule + settingsActivity = new ComponentName(ci.packageName, ci.name); + } else if (ci.metaData != null) { + // old service backed rule + final String configurationActivity = ci.metaData.getString( + ConditionProviderService.META_DATA_CONFIGURATION_ACTIVITY); + if (configurationActivity != null) { + settingsActivity = ComponentName.unflattenFromString(configurationActivity); + } + } } - // new activity backed rule - if (ci instanceof ActivityInfo) { - return new ComponentName(ci.packageName, ci.name); + if (settingsActivity == null || owner == null) { + return settingsActivity; } - // old service backed rule - if (ci.metaData != null) { - final String configurationActivity = ci.metaData.getString( - ConditionProviderService.META_DATA_CONFIGURATION_ACTIVITY); - if (configurationActivity != null) { - return ComponentName.unflattenFromString(configurationActivity); + try { + int ownerUid = pm.getPackageUid(owner, 0); + int configActivityOwnerUid = pm.getPackageUid(settingsActivity.getPackageName(), 0); + if (ownerUid == configActivityOwnerUid) { + return settingsActivity; + } else { + Log.w(TAG, "Config activity not in owner package for " + rule.getName()); + return null; } + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Failed to find config activity"); + return null; } - - return null; } public class RuleNameChangeListener implements ZenRuleNameDialog.PositiveClickListener { diff --git a/src/com/android/settings/notification/zen/ZenRulePreference.java b/src/com/android/settings/notification/zen/ZenRulePreference.java index b8c8354eb3..a265a0776d 100644 --- a/src/com/android/settings/notification/zen/ZenRulePreference.java +++ b/src/com/android/settings/notification/zen/ZenRulePreference.java @@ -168,7 +168,7 @@ public class ZenRulePreference extends TwoTargetPreference { : isEvent ? ZenModeEventRuleSettings.ACTION : ""; ComponentInfo si = mServiceListing.findService(rule.getOwner()); ComponentName settingsActivity = AbstractZenModeAutomaticRulePreferenceController. - getSettingsActivity(rule, si); + getSettingsActivity(mPm, rule, si); mIntent = AbstractZenModeAutomaticRulePreferenceController.getRuleIntent(action, settingsActivity, mId); if (mIntent.resolveActivity(mPm) == null) { diff --git a/src/com/android/settings/overlay/FeatureFactory.java b/src/com/android/settings/overlay/FeatureFactory.java index 58f80fce22..cfdc9eb5b6 100644 --- a/src/com/android/settings/overlay/FeatureFactory.java +++ b/src/com/android/settings/overlay/FeatureFactory.java @@ -23,6 +23,7 @@ import android.util.Log; import androidx.annotation.Nullable; import com.android.settings.R; +import com.android.settings.accessibility.AccessibilitySearchFeatureProvider; import com.android.settings.accounts.AccountFeatureProvider; import com.android.settings.applications.ApplicationFeatureProvider; import com.android.settings.applications.GameSettingsFeatureProvider; @@ -174,6 +175,11 @@ public abstract class FeatureFactory { */ public abstract GameSettingsFeatureProvider getGameSettingsFeatureProvider(); + /** + * Retrieve implementation for Accessibility search index feature. + */ + public abstract AccessibilitySearchFeatureProvider getAccessibilitySearchFeatureProvider(); + public static final class FactoryNotFoundException extends RuntimeException { public FactoryNotFoundException(Throwable throwable) { super("Unable to create factory. Did you misconfigure Proguard?", throwable); diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.java b/src/com/android/settings/overlay/FeatureFactoryImpl.java index 13b7b604de..9890a10964 100644 --- a/src/com/android/settings/overlay/FeatureFactoryImpl.java +++ b/src/com/android/settings/overlay/FeatureFactoryImpl.java @@ -25,6 +25,8 @@ import android.os.UserManager; import androidx.annotation.Keep; +import com.android.settings.accessibility.AccessibilitySearchFeatureProvider; +import com.android.settings.accessibility.AccessibilitySearchFeatureProviderImpl; import com.android.settings.accounts.AccountFeatureProvider; import com.android.settings.accounts.AccountFeatureProviderImpl; import com.android.settings.applications.ApplicationFeatureProvider; @@ -106,6 +108,7 @@ public class FeatureFactoryImpl extends FeatureFactory { private ExtraAppInfoFeatureProvider mExtraAppInfoFeatureProvider; private SecuritySettingsFeatureProvider mSecuritySettingsFeatureProvider; private GameSettingsFeatureProvider mGameSettingsFeatureProvider; + private AccessibilitySearchFeatureProvider mAccessibilitySearchFeatureProvider; @Override public SupportFeatureProvider getSupportFeatureProvider(Context context) { @@ -335,4 +338,12 @@ public class FeatureFactoryImpl extends FeatureFactory { } return mGameSettingsFeatureProvider; } + + @Override + public AccessibilitySearchFeatureProvider getAccessibilitySearchFeatureProvider() { + if (mAccessibilitySearchFeatureProvider == null) { + mAccessibilitySearchFeatureProvider = new AccessibilitySearchFeatureProviderImpl(); + } + return mAccessibilitySearchFeatureProvider; + } } diff --git a/src/com/android/settings/panel/InternetConnectivityPanel.java b/src/com/android/settings/panel/InternetConnectivityPanel.java index e6344d806c..53c0f20ad1 100644 --- a/src/com/android/settings/panel/InternetConnectivityPanel.java +++ b/src/com/android/settings/panel/InternetConnectivityPanel.java @@ -30,6 +30,7 @@ import android.net.wifi.WifiManager; import android.os.Handler; import android.os.HandlerExecutor; import android.os.Looper; +import android.provider.Settings; import android.telephony.ServiceState; import android.telephony.SubscriptionManager; import android.telephony.TelephonyCallback; @@ -84,12 +85,13 @@ public class InternetConnectivityPanel implements PanelContent, LifecycleObserve } if (TextUtils.equals(intent.getAction(), WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) { - showProgressBar(); + updateProgressBar(); updatePanelTitle(); return; } if (TextUtils.equals(intent.getAction(), WifiManager.NETWORK_STATE_CHANGED_ACTION)) { + updateProgressBar(); updatePanelTitle(); } } @@ -110,13 +112,40 @@ public class InternetConnectivityPanel implements PanelContent, LifecycleObserve private int mDefaultDataSubid = SubscriptionManager.INVALID_SUBSCRIPTION_ID; // Wi-Fi scanning progress bar + protected HandlerInjector mHandlerInjector; protected boolean mIsProgressBarVisible; - protected final Runnable mHideProgressBarRunnable = () -> { + protected boolean mIsScanningSubTitleShownOnce; + protected Runnable mHideProgressBarRunnable = () -> { setProgressBarVisible(false); }; + protected Runnable mHideScanningSubTitleRunnable = () -> { + mIsScanningSubTitleShownOnce = true; + updatePanelTitle(); + }; + + /** + * Wrapper for testing compatibility. + */ + @VisibleForTesting + static class HandlerInjector { + protected final Handler mHandler; + + HandlerInjector(Context context) { + mHandler = context.getMainThreadHandler(); + } + + public void postDelay(Runnable runnable) { + mHandler.postDelayed(runnable, 2000 /* delay millis */); + } + + public void removeCallbacks(Runnable runnable) { + mHandler.removeCallbacks(runnable); + } + } private InternetConnectivityPanel(Context context) { mContext = context.getApplicationContext(); + mHandlerInjector = new HandlerInjector(context); mIsProviderModelEnabled = Utils.isProviderModelEnabled(mContext); mInternetUpdater = new InternetUpdater(context, null /* Lifecycle */, this); @@ -150,7 +179,7 @@ public class InternetConnectivityPanel implements PanelContent, LifecycleObserve mTelephonyManager.registerTelephonyCallback( new HandlerExecutor(new Handler(Looper.getMainLooper())), mTelephonyCallback); mContext.registerReceiver(mWifiStateReceiver, mWifiStateFilter); - showProgressBar(); + updateProgressBar(); updatePanelTitle(); } @@ -165,7 +194,8 @@ public class InternetConnectivityPanel implements PanelContent, LifecycleObserve mConnectivityListener.stop(); mTelephonyManager.unregisterTelephonyCallback(mTelephonyCallback); mContext.unregisterReceiver(mWifiStateReceiver); - mContext.getMainThreadHandler().removeCallbacks(mHideProgressBarRunnable); + mHandlerInjector.removeCallbacks(mHideProgressBarRunnable); + mHandlerInjector.removeCallbacks(mHideScanningSubTitleRunnable); } /** @@ -206,7 +236,10 @@ public class InternetConnectivityPanel implements PanelContent, LifecycleObserve @Override public Intent getSeeMoreIntent() { - return null; + // Don't remove the see more intent for non-provider model design. This intent will be + // used when isCustomizedButtonUsed() returns false. + return new Intent(Settings.ACTION_WIRELESS_SETTINGS) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } @Override @@ -246,6 +279,7 @@ public class InternetConnectivityPanel implements PanelContent, LifecycleObserve */ @Override public void onAirplaneModeChanged(boolean isAirplaneModeOn) { + log("onAirplaneModeChanged: isAirplaneModeOn:" + isAirplaneModeOn); updatePanelTitle(); } @@ -254,6 +288,7 @@ public class InternetConnectivityPanel implements PanelContent, LifecycleObserve */ @Override public void onWifiEnabledChanged(boolean enabled) { + log("onWifiEnabledChanged: enabled:" + enabled); updatePanelTitle(); } @@ -305,13 +340,6 @@ public class InternetConnectivityPanel implements PanelContent, LifecycleObserve return; } - if (mIsProgressBarVisible) { - // When the Wi-Fi scan result callback is received - // Sub-Title: Searching for networks... - mSubtitle = SUBTITLE_TEXT_SEARCHING_FOR_NETWORKS; - return; - } - if (mInternetUpdater.isAirplaneModeOn()) { return; } @@ -319,11 +347,18 @@ public class InternetConnectivityPanel implements PanelContent, LifecycleObserve final List<ScanResult> wifiList = mWifiManager.getScanResults(); if (wifiList != null && wifiList.size() != 0) { // When the Wi-Fi scan result is not empty - // Sub-Title: Select the network you want to use for data + // Sub-Title: Tap a network to connect mSubtitle = SUBTITLE_TEXT_TAP_A_NETWORK_TO_CONNECT; return; } + if (!mIsScanningSubTitleShownOnce && mIsProgressBarVisible) { + // When the Wi-Fi scan result callback is received + // Sub-Title: Searching for networks... + mSubtitle = SUBTITLE_TEXT_SEARCHING_FOR_NETWORKS; + return; + } + // Sub-Title: // show non_carrier_network_unavailable // - while Wi-Fi on + no Wi-Fi item @@ -353,7 +388,7 @@ public class InternetConnectivityPanel implements PanelContent, LifecycleObserve mSubtitle = SUBTITLE_TEXT_NON_CARRIER_NETWORK_UNAVAILABLE; } - protected void showProgressBar() { + protected void updateProgressBar() { if (mWifiManager == null || !mInternetUpdater.isWifiEnabled()) { setProgressBarVisible(false); return; @@ -362,8 +397,9 @@ public class InternetConnectivityPanel implements PanelContent, LifecycleObserve setProgressBarVisible(true); List<ScanResult> wifiScanResults = mWifiManager.getScanResults(); if (wifiScanResults != null && wifiScanResults.size() > 0) { - mContext.getMainThreadHandler().postDelayed(mHideProgressBarRunnable, - 2000 /* delay millis */); + mHandlerInjector.postDelay(mHideProgressBarRunnable); + } else if (!mIsScanningSubTitleShownOnce) { + mHandlerInjector.postDelay(mHideScanningSubTitleRunnable); } } diff --git a/src/com/android/settings/password/BiometricFragment.java b/src/com/android/settings/password/BiometricFragment.java index 7666a3609c..e4c08e73ff 100644 --- a/src/com/android/settings/password/BiometricFragment.java +++ b/src/com/android/settings/password/BiometricFragment.java @@ -47,7 +47,6 @@ public class BiometricFragment extends InstrumentedFragment { private int mUserId; // Created/Initialized once and retained - private PromptInfo mPromptInfo; private BiometricPrompt mBiometricPrompt; private CancellationSignal mCancellationSignal; @@ -127,7 +126,7 @@ public class BiometricFragment extends InstrumentedFragment { final Bundle bundle = getArguments(); final PromptInfo promptInfo = bundle.getParcelable(KEY_PROMPT_INFO); - final BiometricPrompt.Builder builder = new BiometricPrompt.Builder(getContext()) + mBiometricPrompt = new BiometricPrompt.Builder(getContext()) .setTitle(promptInfo.getTitle()) .setUseDefaultTitle() // use default title if title is null/empty .setDeviceCredentialAllowed(true) @@ -140,13 +139,19 @@ public class BiometricFragment extends InstrumentedFragment { .setConfirmationRequired(promptInfo.isConfirmationRequested()) .setDisallowBiometricsIfPolicyExists( promptInfo.isDisallowBiometricsIfPolicyExists()) - .setReceiveSystemEvents(true); + .setReceiveSystemEvents(true) + .build(); + } - mBiometricPrompt = builder.build(); - mCancellationSignal = new CancellationSignal(); + @Override + public void onResume() { + super.onResume(); - mBiometricPrompt.authenticateUser(mCancellationSignal, mClientExecutor, - mAuthenticationCallback, mUserId); + if (mCancellationSignal == null) { + mCancellationSignal = new CancellationSignal(); + mBiometricPrompt.authenticateUser(mCancellationSignal, mClientExecutor, + mAuthenticationCallback, mUserId); + } } @Override @@ -154,4 +159,3 @@ public class BiometricFragment extends InstrumentedFragment { return SettingsEnums.BIOMETRIC_FRAGMENT; } } - diff --git a/src/com/android/settings/password/ChooseLockPattern.java b/src/com/android/settings/password/ChooseLockPattern.java index 53898a6eb9..b60b427360 100644 --- a/src/com/android/settings/password/ChooseLockPattern.java +++ b/src/com/android/settings/password/ChooseLockPattern.java @@ -16,6 +16,8 @@ package com.android.settings.password; +import static android.view.View.ACCESSIBILITY_LIVE_REGION_POLITE; + import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL; import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_UNIFICATION_PROFILE_ID; @@ -496,6 +498,7 @@ public class ChooseLockPattern extends SettingsActivity { R.layout.choose_lock_pattern, container, false); updateActivityTitle(); layout.setHeaderText(getActivity().getTitle()); + layout.getHeaderTextView().setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE); if (getResources().getBoolean(R.bool.config_lock_pattern_minimal_ui)) { View iconView = layout.findViewById(R.id.sud_layout_icon); if (iconView != null) { diff --git a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java index 6241cc91cf..03e83a4f61 100644 --- a/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java +++ b/src/com/android/settings/password/ConfirmDeviceCredentialActivity.java @@ -150,17 +150,6 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity { } }; - private String getStringForError(int errorCode) { - switch (errorCode) { - case BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED: - return getString(com.android.internal.R.string.biometric_error_user_canceled); - case BiometricConstants.BIOMETRIC_ERROR_CANCELED: - return getString(com.android.internal.R.string.biometric_error_canceled); - default: - return null; - } - } - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/src/com/android/settings/search/SearchFeatureProvider.java b/src/com/android/settings/search/SearchFeatureProvider.java index 18c802c219..4c39b9c0c1 100644 --- a/src/com/android/settings/search/SearchFeatureProvider.java +++ b/src/com/android/settings/search/SearchFeatureProvider.java @@ -21,7 +21,6 @@ import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO; import android.annotation.NonNull; import android.app.Activity; import android.app.ActivityOptions; -import android.app.settings.SettingsEnums; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -43,6 +42,7 @@ import com.google.android.setupcompat.util.WizardManagerHelper; */ public interface SearchFeatureProvider { + String KEY_HOMEPAGE_SEARCH_BAR = "homepage_search_bar"; int REQUEST_CODE = 501; /** @@ -100,8 +100,9 @@ public interface SearchFeatureProvider { FeatureFactory.getFactory(context).getSlicesFeatureProvider() .indexSliceDataAsync(context); + FeatureFactory.getFactory(context).getMetricsFeatureProvider() - .action(context, SettingsEnums.ACTION_SEARCH_RESULTS); + .logSettingsTileClick(KEY_HOMEPAGE_SEARCH_BAR, pageId); final Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(activity).toBundle(); activity.startActivityForResult(intent, REQUEST_CODE, bundle); }); diff --git a/src/com/android/settings/sim/ChooseSimActivity.java b/src/com/android/settings/sim/ChooseSimActivity.java index f8bdc3011b..d0ccc4c9d0 100644 --- a/src/com/android/settings/sim/ChooseSimActivity.java +++ b/src/com/android/settings/sim/ChooseSimActivity.java @@ -19,6 +19,7 @@ package com.android.settings.sim; import android.app.Activity; import android.content.Context; import android.content.Intent; +import android.icu.text.MessageFormat; import android.os.Bundle; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; @@ -41,10 +42,12 @@ import com.google.android.setupdesign.items.IItem; import com.google.android.setupdesign.items.Item; import com.google.android.setupdesign.items.ItemGroup; import com.google.android.setupdesign.items.RecyclerItemAdapter; -import com.google.android.setupdesign.view.HeaderRecyclerView; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Locale; +import java.util.Map; /** Activity to show a list of profiles for user to choose. */ public class ChooseSimActivity extends Activity @@ -104,13 +107,17 @@ public class ChooseSimActivity extends Activity } GlifLayout layout = findViewById(R.id.glif_layout); - TextView textView = findViewById(R.id.subtitle); int subscriptionCount = mEmbeddedSubscriptions.size(); if (mHasPsim) { // Choose a number to use subscriptionCount++; } layout.setHeaderText(getString(R.string.choose_sim_title)); - textView.setText(getString(R.string.choose_sim_text, subscriptionCount)); + MessageFormat msgFormat = new MessageFormat( + getString(R.string.choose_sim_text), + Locale.getDefault()); + Map<String, Object> arguments = new HashMap<>(); + arguments.put("count", subscriptionCount); + layout.setDescriptionText(msgFormat.format(arguments)); displaySubscriptions(); @@ -217,7 +224,7 @@ public class ChooseSimActivity extends Activity private void displaySubscriptions() { View rootView = findViewById(android.R.id.content); - GlifRecyclerLayout layout = rootView.findViewById(R.id.recycler_list); + GlifRecyclerLayout layout = rootView.findViewById(R.id.glif_layout); RecyclerItemAdapter adapter = (RecyclerItemAdapter) layout.getAdapter(); adapter.setOnItemSelectedListener(this); mItemGroup = (ItemGroup) adapter.getRootItemHierarchy(); @@ -265,10 +272,6 @@ public class ChooseSimActivity extends Activity item.setId(index++); mItemGroup.addChild(item); } - - // This removes the unused header artifact from GlifRecyclerLayout. - HeaderRecyclerView rv = (HeaderRecyclerView) layout.getRecyclerView(); - rv.getHeader().setVisibility(View.GONE); } private void updateSubscriptions() { diff --git a/src/com/android/settings/widget/LoadingViewController.java b/src/com/android/settings/widget/LoadingViewController.java index 294e55e7ea..66eebf387b 100644 --- a/src/com/android/settings/widget/LoadingViewController.java +++ b/src/com/android/settings/widget/LoadingViewController.java @@ -22,34 +22,66 @@ import android.view.View; import android.view.animation.Animation; import android.view.animation.AnimationUtils; +import androidx.annotation.Nullable; + /** - * A helper class that manages show/hide loading spinner. + * A helper class that manages show/hide loading spinner, content view and empty view (optional). */ public class LoadingViewController { private static final long DELAY_SHOW_LOADING_CONTAINER_THRESHOLD_MS = 100L; - public final Handler mFgHandler; - public final View mLoadingView; - public final View mContentView; + private final Handler mFgHandler; + private final View mLoadingView; + private final View mContentView; + private final View mEmptyView; public LoadingViewController(View loadingView, View contentView) { + this(loadingView, contentView, null /* emptyView*/); + } + + public LoadingViewController(View loadingView, View contentView, @Nullable View emptyView) { mLoadingView = loadingView; mContentView = contentView; + mEmptyView = emptyView; mFgHandler = new Handler(Looper.getMainLooper()); } private Runnable mShowLoadingContainerRunnable = new Runnable() { public void run() { - handleLoadingContainer(false /* done */, false /* animate */); + showLoadingView(); } }; + /** + * Shows content view and hides loading view & empty view. + */ public void showContent(boolean animate) { // Cancel any pending task to show the loading animation and show the list of // apps directly. mFgHandler.removeCallbacks(mShowLoadingContainerRunnable); - handleLoadingContainer(true /* show */, animate); + handleLoadingContainer(true /* showContent */, false /* showEmpty*/, animate); + } + + /** + * Shows empty view and hides loading view & content view. + */ + public void showEmpty(boolean animate) { + if (mEmptyView == null) { + return; + } + + // Cancel any pending task to show the loading animation and show the list of + // apps directly. + mFgHandler.removeCallbacks(mShowLoadingContainerRunnable); + handleLoadingContainer(false /* showContent */, true /* showEmpty */, animate); + } + + /** + * Shows loading view and hides content view & empty view. + */ + public void showLoadingView() { + handleLoadingContainer(false /* showContent */, false /* showEmpty */, false /* animate */); } public void showLoadingViewDelayed() { @@ -57,8 +89,9 @@ public class LoadingViewController { mShowLoadingContainerRunnable, DELAY_SHOW_LOADING_CONTAINER_THRESHOLD_MS); } - public void handleLoadingContainer(boolean done, boolean animate) { - handleLoadingContainer(mLoadingView, mContentView, done, animate); + private void handleLoadingContainer(boolean showContent, boolean showEmpty, boolean animate) { + handleLoadingContainer(mLoadingView, mContentView, mEmptyView, + showContent, showEmpty, animate); } /** @@ -75,6 +108,25 @@ public class LoadingViewController { setViewShown(content, done, animate); } + /** + * Show/hide loading view and content view and empty view. + * + * @param loading The loading spinner view + * @param content The content view + * @param empty The empty view shows no item summary to users. + * @param showContent If true, content is set visible and loading is set invisible. + * @param showEmpty If true, empty is set visible and loading is set invisible. + * @param animate Whether or not content/loading views should animate in/out. + */ + public static void handleLoadingContainer(View loading, View content, View empty, + boolean showContent, boolean showEmpty, boolean animate) { + if (empty != null) { + setViewShown(empty, showEmpty, animate); + } + setViewShown(content, showContent, animate); + setViewShown(loading, !showContent && !showEmpty, animate); + } + private static void setViewShown(final View view, boolean shown, boolean animate) { if (animate) { Animation animation = AnimationUtils.loadAnimation(view.getContext(), diff --git a/src/com/android/settings/widget/SeekBarPreference.java b/src/com/android/settings/widget/SeekBarPreference.java index 47bb28608d..62a19b945e 100644 --- a/src/com/android/settings/widget/SeekBarPreference.java +++ b/src/com/android/settings/widget/SeekBarPreference.java @@ -57,6 +57,7 @@ public class SeekBarPreference extends RestrictedPreference private SeekBar mSeekBar; private boolean mShouldBlink; private int mAccessibilityRangeInfoType = AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_INT; + private CharSequence mOverrideSeekBarStateDescription; private CharSequence mSeekBarContentDescription; private CharSequence mSeekBarStateDescription; @@ -162,6 +163,9 @@ public class SeekBarPreference extends RestrictedPreference mAccessibilityRangeInfoType, rangeInfo.getMin(), rangeInfo.getMax(), rangeInfo.getCurrent())); } + if (mOverrideSeekBarStateDescription != null) { + info.setStateDescription(mOverrideSeekBarStateDescription); + } } }); } @@ -348,6 +352,13 @@ public class SeekBarPreference extends RestrictedPreference } } + /** + * Overrides the state description of {@link SeekBar} with given content. + */ + public void overrideSeekBarStateDescription(CharSequence stateDescription) { + mOverrideSeekBarStateDescription = stateDescription; + } + @Override protected Parcelable onSaveInstanceState() { /* diff --git a/src/com/android/settings/widget/SettingsMainSwitchPreference.java b/src/com/android/settings/widget/SettingsMainSwitchPreference.java index de7d6926d0..09e1ca4cdc 100644 --- a/src/com/android/settings/widget/SettingsMainSwitchPreference.java +++ b/src/com/android/settings/widget/SettingsMainSwitchPreference.java @@ -82,15 +82,15 @@ public class SettingsMainSwitchPreference extends TwoStatePreference implements holder.setDividerAllowedAbove(false); holder.setDividerAllowedBelow(false); - mMainSwitchBar = (SettingsMainSwitchBar) holder.findViewById(R.id.main_switch_bar); - mMainSwitchBar.show(); if (mRestrictedHelper != null) { mEnforcedAdmin = mRestrictedHelper.checkRestrictionEnforced(); } - updateStatus(isChecked()); - registerListenerToSwitchBar(); - - if (!mIsVisible) { + mMainSwitchBar = (SettingsMainSwitchBar) holder.findViewById(R.id.main_switch_bar); + if (mIsVisible) { + mMainSwitchBar.show(); + updateStatus(isChecked()); + registerListenerToSwitchBar(); + } else { mMainSwitchBar.hide(); } } diff --git a/src/com/android/settings/wifi/WifiPickerTrackerHelper.java b/src/com/android/settings/wifi/WifiPickerTrackerHelper.java index aaab3685f7..424c818cb6 100644 --- a/src/com/android/settings/wifi/WifiPickerTrackerHelper.java +++ b/src/com/android/settings/wifi/WifiPickerTrackerHelper.java @@ -77,7 +77,7 @@ public class WifiPickerTrackerHelper implements LifecycleObserver { Process.THREAD_PRIORITY_BACKGROUND); mWorkerThread.start(); - mWifiPickerTracker = FeatureFactory.getFactory(context) + mWifiPickerTracker = FeatureFactory.getFactory(context) .getWifiTrackerLibProvider() .createWifiPickerTracker(lifecycle, context, new Handler(Looper.getMainLooper()), @@ -139,8 +139,8 @@ public class WifiPickerTrackerHelper implements LifecycleObserver { return true; } - /** Confirms connection of the carrier network */ - public boolean isActiveCarrierNetwork() { + /** Confirms connection of the carrier network connected with the internet access */ + public boolean isCarrierNetworkActive() { final MergedCarrierEntry mergedCarrierEntry = mWifiPickerTracker.getMergedCarrierEntry(); if (mergedCarrierEntry != null) { return mergedCarrierEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED |