1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.core; 18 19 import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_CONTROLLER; 20 import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_FOR_WORK; 21 import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_KEY; 22 23 import android.annotation.XmlRes; 24 import android.content.Context; 25 import android.os.Bundle; 26 import android.text.TextUtils; 27 import android.util.Log; 28 29 import androidx.annotation.NonNull; 30 import androidx.preference.PreferenceManager; 31 import androidx.preference.PreferenceScreen; 32 33 import com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag; 34 import com.android.settingslib.core.AbstractPreferenceController; 35 36 import org.xmlpull.v1.XmlPullParserException; 37 38 import java.io.IOException; 39 import java.util.ArrayList; 40 import java.util.List; 41 import java.util.Set; 42 import java.util.TreeSet; 43 44 /** 45 * Helper to load {@link BasePreferenceController} lists from Xml. 46 */ 47 public class PreferenceControllerListHelper { 48 49 private static final String TAG = "PrefCtrlListHelper"; 50 51 /** 52 * Instantiates a list of controller based on xml definition. 53 */ 54 @NonNull getPreferenceControllersFromXml(Context context, @XmlRes int xmlResId)55 public static List<BasePreferenceController> getPreferenceControllersFromXml(Context context, 56 @XmlRes int xmlResId) { 57 final List<BasePreferenceController> controllers = new ArrayList<>(); 58 List<Bundle> preferenceMetadata; 59 try { 60 preferenceMetadata = PreferenceXmlParserUtils.extractMetadata(context, xmlResId, 61 MetadataFlag.FLAG_NEED_KEY | MetadataFlag.FLAG_NEED_PREF_CONTROLLER 62 | MetadataFlag.FLAG_INCLUDE_PREF_SCREEN | MetadataFlag.FLAG_FOR_WORK); 63 } catch (IOException | XmlPullParserException e) { 64 Log.e(TAG, "Failed to parse preference xml for getting controllers", e); 65 return controllers; 66 } 67 68 for (Bundle metadata : preferenceMetadata) { 69 final String controllerName = metadata.getString(METADATA_CONTROLLER); 70 if (TextUtils.isEmpty(controllerName)) { 71 continue; 72 } 73 BasePreferenceController controller; 74 try { 75 controller = BasePreferenceController.createInstance(context, controllerName); 76 } catch (IllegalStateException e) { 77 Log.d(TAG, "Could not find Context-only controller for pref: " + controllerName); 78 final String key = metadata.getString(METADATA_KEY); 79 final boolean isWorkProfile = metadata.getBoolean(METADATA_FOR_WORK, false); 80 if (TextUtils.isEmpty(key)) { 81 Log.w(TAG, "Controller requires key but it's not defined in xml: " 82 + controllerName); 83 continue; 84 } 85 try { 86 controller = BasePreferenceController.createInstance(context, controllerName, 87 key, isWorkProfile); 88 } catch (IllegalStateException e2) { 89 Log.w(TAG, "Cannot instantiate controller from reflection: " + controllerName); 90 continue; 91 } 92 } 93 controllers.add(controller); 94 } 95 return controllers; 96 } 97 98 /** 99 * Checks if the given PreferenceScreen will be empty due to all preferences being unavailable. 100 * 101 * @param xmlResId resource id of the PreferenceScreen to check 102 * @return {@code true} if none of the preferences in the given screen will appear 103 */ areAllPreferencesUnavailable(@onNull Context context, @NonNull PreferenceManager preferenceManager, @XmlRes int xmlResId)104 public static boolean areAllPreferencesUnavailable(@NonNull Context context, 105 @NonNull PreferenceManager preferenceManager, @XmlRes int xmlResId) { 106 PreferenceScreen screen = preferenceManager.inflateFromResource(context, xmlResId, 107 /* rootPreferences= */ null); 108 List<BasePreferenceController> preferenceControllers = 109 getPreferenceControllersFromXml(context, xmlResId); 110 if (screen.getPreferenceCount() != preferenceControllers.size()) { 111 // There are some preferences without controllers, which will show regardless. 112 return false; 113 } 114 return preferenceControllers.stream().noneMatch(BasePreferenceController::isAvailable); 115 } 116 117 /** 118 * Return a sub list of {@link AbstractPreferenceController} to only contain controller that 119 * doesn't exist in filter. 120 * 121 * @param filter The filter. This list will be unchanged. 122 * @param input This list will be filtered into a sublist and element is kept 123 * IFF the controller key is not used by anything from {@param filter}. 124 */ 125 @NonNull filterControllers( @onNull List<BasePreferenceController> input, List<AbstractPreferenceController> filter)126 public static List<BasePreferenceController> filterControllers( 127 @NonNull List<BasePreferenceController> input, 128 List<AbstractPreferenceController> filter) { 129 if (input == null || filter == null) { 130 return input; 131 } 132 final Set<String> keys = new TreeSet<>(); 133 final List<BasePreferenceController> filteredList = new ArrayList<>(); 134 for (AbstractPreferenceController controller : filter) { 135 final String key = controller.getPreferenceKey(); 136 if (key != null) { 137 keys.add(key); 138 } 139 } 140 for (BasePreferenceController controller : input) { 141 if (keys.contains(controller.getPreferenceKey())) { 142 Log.w(TAG, controller.getPreferenceKey() + " already has a controller"); 143 continue; 144 } 145 filteredList.add(controller); 146 } 147 return filteredList; 148 } 149 150 } 151