1 /* 2 * Copyright (C) 2022 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.tv.settings.customization; 18 19 import android.content.Context; 20 import android.util.Log; 21 22 import androidx.annotation.Nullable; 23 import androidx.preference.Preference; 24 import androidx.preference.PreferenceGroup; 25 import androidx.preference.PreferenceScreen; 26 27 import java.util.ArrayList; 28 import java.util.Arrays; 29 import java.util.Iterator; 30 import java.util.List; 31 32 /** 33 * This is responsible for building the PreferenceScreen according to the 34 * Partner provided ordered preference list. 35 */ 36 public final class PartnerPreferencesMerger { 37 private static final String TAG = "PartnerPreferencesMerger"; 38 mergePreferences( Context context, PreferenceScreen preferenceScreen, String settingsScreen)39 public static void mergePreferences( 40 Context context, PreferenceScreen preferenceScreen, String settingsScreen) { 41 /* 42 High level algorithm of adding new preferences in the desired order 43 1. Build partner provided new preferences if any. 44 45 2. Add the preferences in 1. to the existing TvSettings PreferenceScreen. 46 47 3. Recursively expand and parse the partner provided ordered string array 48 of preference keys. Each preference key can either be that of a PreferenceGroup 49 or a base preference. For every PreferenceGroup there is an array listing the 50 preferences it contains. 51 52 4. Recursively clone every preference in current TvSettings PreferenceScreen. 53 The preference can either be a preference or a PreferenceGroup. Remove all the 54 preferences in each PreferenceGroup. 55 56 5. Iterate through the ordered array in step 3, recursively adding preferences 57 in all PreferenceGroups. 58 */ 59 final PartnerResourcesParser partnerResourcesParser = new PartnerResourcesParser( 60 context, settingsScreen); 61 for (final Preference newPartnerPreference : partnerResourcesParser.buildPreferences()) { 62 preferenceScreen.addPreference(newPartnerPreference); 63 } 64 65 final String[] orderedPreferenceKeys = partnerResourcesParser.getOrderedPreferences(); 66 67 // Clone the existing tv settings PreferenceScreen. All the preferences 68 // will be removed from this screen to avoid multiple re-orderings as 69 // the ordered preferences are being built 70 final Preference[] combinedSettingsPreferences = clonePreferenceScreen(preferenceScreen); 71 preferenceScreen.removeAll(); 72 73 addPreferences( 74 Arrays.stream(orderedPreferenceKeys).iterator(), 75 preferenceScreen, 76 combinedSettingsPreferences 77 ); 78 79 // PreferenceScreen preferences are re-ordered whenever the notifyHierarchyChanged() 80 // method is invoked. It is package private and thus indirectly triggered by removing 81 // a preference that does not exist. Adding / removing a new preference always invokes 82 // notifyHierarchyChanged() 83 final Preference triggerReorderPreference = new Preference(preferenceScreen.getContext()); 84 preferenceScreen.removePreference(triggerReorderPreference); 85 } 86 87 /** 88 * Recursively iterates through all the preferences in PreferenceScreen and all 89 * PreferenceGroups in it doing a clone by reference. 90 * @param preferenceScreen current Tv Settings screen shown to the user 91 * @return Array of all preferences in present in the preferenceScreen 92 */ clonePreferenceScreen(PreferenceScreen preferenceScreen)93 private static Preference[] clonePreferenceScreen(PreferenceScreen preferenceScreen) { 94 return clonePreferencesInPreferenceGroup(preferenceScreen) 95 .toArray(Preference[]::new); 96 } 97 clonePreferencesInPreferenceGroup( PreferenceGroup preferenceGroup)98 private static List<Preference> clonePreferencesInPreferenceGroup( 99 PreferenceGroup preferenceGroup) { 100 final List<Preference> preferences = new ArrayList<>(); 101 for (int index = 0; index < preferenceGroup.getPreferenceCount(); index++) { 102 final Preference preference = preferenceGroup.getPreference(index); 103 if (preference instanceof PreferenceGroup) { 104 final List<Preference> nestedPreferences = 105 clonePreferencesInPreferenceGroup((PreferenceGroup) preference); 106 // Remove all preferences in the PreferenceGroup since the logic 107 // to sort the preferences involves iterating through each preference 108 // key. Having these preferences in a PreferenceGroup will result 109 // in these nested preferences being added twice in the final list 110 // of ordered preferences. 111 ((PreferenceGroup) preference).removeAll(); 112 preferences.add(preference); 113 preferences.addAll(nestedPreferences); 114 } else { 115 preferences.add(preference); 116 } 117 } 118 return preferences; 119 } 120 addPreferences( Iterator<String> partnerPreferenceKeyIterator, PreferenceGroup preferenceGroup, Preference[] tvSettingsPreferences)121 private static void addPreferences( 122 Iterator<String> partnerPreferenceKeyIterator, 123 PreferenceGroup preferenceGroup, 124 Preference[] tvSettingsPreferences) { 125 int order = 0; 126 while (partnerPreferenceKeyIterator.hasNext()) { 127 final String preferenceKey = partnerPreferenceKeyIterator.next(); 128 if (preferenceKey.equals(PartnerResourcesParser.PREFERENCE_GROUP_END_INDICATOR)) { 129 break; 130 } 131 132 final Preference preference = findPreference(preferenceKey, tvSettingsPreferences); 133 if (preference == null) { 134 Log.i(TAG, "Partner provided preference key: " 135 + preferenceKey + " is not defined anywhere"); 136 continue; 137 } 138 if (preference instanceof PreferenceGroup) { 139 addPreferences(partnerPreferenceKeyIterator, 140 (PreferenceGroup) preference, tvSettingsPreferences); 141 } 142 preference.setOrder(++order); 143 preferenceGroup.addPreference(preference); 144 } 145 } 146 147 @Nullable findPreference(String key, Preference[] preferences)148 private static Preference findPreference(String key, Preference[] preferences) { 149 for (final Preference preference : preferences) { 150 if (preference.getKey().equals(key)) { 151 return preference; 152 } 153 } 154 return null; 155 } 156 } 157