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.NonNull;
24 import android.annotation.XmlRes;
25 import android.content.Context;
26 import android.os.Bundle;
27 import android.text.TextUtils;
28 import android.util.Log;
29 
30 import com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag;
31 import com.android.settingslib.core.AbstractPreferenceController;
32 
33 import org.xmlpull.v1.XmlPullParserException;
34 
35 import java.io.IOException;
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.Set;
39 import java.util.TreeSet;
40 
41 /**
42  * Helper to load {@link BasePreferenceController} lists from Xml.
43  */
44 public class PreferenceControllerListHelper {
45 
46     private static final String TAG = "PrefCtrlListHelper";
47 
48     /**
49      * Instantiates a list of controller based on xml definition.
50      */
51     @NonNull
getPreferenceControllersFromXml(Context context, @XmlRes int xmlResId)52     public static List<BasePreferenceController> getPreferenceControllersFromXml(Context context,
53             @XmlRes int xmlResId) {
54         final List<BasePreferenceController> controllers = new ArrayList<>();
55         List<Bundle> preferenceMetadata;
56         try {
57             preferenceMetadata = PreferenceXmlParserUtils.extractMetadata(context, xmlResId,
58                     MetadataFlag.FLAG_NEED_KEY | MetadataFlag.FLAG_NEED_PREF_CONTROLLER
59                             | MetadataFlag.FLAG_INCLUDE_PREF_SCREEN  | MetadataFlag.FLAG_FOR_WORK);
60         } catch (IOException | XmlPullParserException e) {
61             Log.e(TAG, "Failed to parse preference xml for getting controllers", e);
62             return controllers;
63         }
64 
65         for (Bundle metadata : preferenceMetadata) {
66             final String controllerName = metadata.getString(METADATA_CONTROLLER);
67             if (TextUtils.isEmpty(controllerName)) {
68                 continue;
69             }
70             BasePreferenceController controller;
71             try {
72                 controller = BasePreferenceController.createInstance(context, controllerName);
73             } catch (IllegalStateException e) {
74                 Log.d(TAG, "Could not find Context-only controller for pref: " + controllerName);
75                 final String key = metadata.getString(METADATA_KEY);
76                 final boolean isWorkProfile = metadata.getBoolean(METADATA_FOR_WORK, false);
77                 if (TextUtils.isEmpty(key)) {
78                     Log.w(TAG, "Controller requires key but it's not defined in xml: "
79                             + controllerName);
80                     continue;
81                 }
82                 try {
83                     controller = BasePreferenceController.createInstance(context, controllerName,
84                             key, isWorkProfile);
85                 } catch (IllegalStateException e2) {
86                     Log.w(TAG, "Cannot instantiate controller from reflection: " + controllerName);
87                     continue;
88                 }
89             }
90             controllers.add(controller);
91         }
92         return controllers;
93     }
94 
95     /**
96      * Return a sub list of {@link AbstractPreferenceController} to only contain controller that
97      * doesn't exist in filter.
98      *
99      * @param filter The filter. This list will be unchanged.
100      * @param input  This list will be filtered into a sublist and element is kept
101      *               IFF the controller key is not used by anything from {@param filter}.
102      */
103     @NonNull
filterControllers( @onNull List<BasePreferenceController> input, List<AbstractPreferenceController> filter)104     public static List<BasePreferenceController> filterControllers(
105             @NonNull List<BasePreferenceController> input,
106             List<AbstractPreferenceController> filter) {
107         if (input == null || filter == null) {
108             return input;
109         }
110         final Set<String> keys = new TreeSet<>();
111         final List<BasePreferenceController> filteredList = new ArrayList<>();
112         for (AbstractPreferenceController controller : filter) {
113             final String key = controller.getPreferenceKey();
114             if (key != null) {
115                 keys.add(key);
116             }
117         }
118         for (BasePreferenceController controller : input) {
119             if (keys.contains(controller.getPreferenceKey())) {
120                 Log.w(TAG, controller.getPreferenceKey() + " already has a controller");
121                 continue;
122             }
123             filteredList.add(controller);
124         }
125         return filteredList;
126     }
127 
128 }
129