1package com.android.carrierconfig;
2
3import android.content.Context;
4import android.os.Build;
5import android.os.PersistableBundle;
6import android.service.carrier.CarrierIdentifier;
7import android.service.carrier.CarrierService;
8import android.telephony.CarrierConfigManager;
9import android.telephony.TelephonyManager;
10import android.util.Log;
11
12import org.xmlpull.v1.XmlPullParser;
13import org.xmlpull.v1.XmlPullParserException;
14import org.xmlpull.v1.XmlPullParserFactory;
15
16import java.io.File;
17import java.io.FileOutputStream;
18import java.io.IOException;
19import java.io.InputStream;
20import java.util.HashMap;
21
22import com.android.internal.util.FastXmlSerializer;
23
24/**
25 * Provides network overrides for carrier configuration.
26 *
27 * The configuration available through CarrierConfigManager is a combination of default values,
28 * default network overrides, and carrier overrides. The default network overrides are provided by
29 * this service. For a given network, we look for a matching XML file in our assets folder, and
30 * return the PersistableBundle from that file. Assets are preferred over Resources because resource
31 * overlays only support using MCC+MNC and that doesn't work with MVNOs. The only resource file used
32 * is vendor.xml, to provide vendor-specific overrides.
33 */
34public class DefaultCarrierConfigService extends CarrierService {
35
36    private static final String TAG = "DefaultCarrierConfigService";
37
38    private XmlPullParserFactory mFactory;
39
40    public DefaultCarrierConfigService() {
41        Log.d(TAG, "Service created");
42        mFactory = null;
43    }
44
45    /**
46     * Returns per-network overrides for carrier configuration.
47     *
48     * This returns a carrier config bundle appropriate for the given network by reading data from
49     * files in our assets folder. First we look for a file named after the MCC+MNC of {@code id}
50     * and then we read res/xml/vendor.xml. Both files may contain multiple bundles with filters on
51     * them. All the matching bundles are flattened to return one carrier config bundle.
52     */
53    @Override
54    public PersistableBundle onLoadConfig(CarrierIdentifier id) {
55        Log.d(TAG, "Config being fetched");
56
57        if (id == null) {
58            return null;
59        }
60
61
62        PersistableBundle config = null;
63        try {
64            synchronized (this) {
65                if (mFactory == null) {
66                    mFactory = XmlPullParserFactory.newInstance();
67                }
68            }
69
70            XmlPullParser parser = mFactory.newPullParser();
71            String fileName = "carrier_config_" + id.getMcc() + id.getMnc() + ".xml";
72            parser.setInput(getApplicationContext().getAssets().open(fileName), "utf-8");
73            config = readConfigFromXml(parser, id);
74        }
75        catch (IOException | XmlPullParserException e) {
76            Log.d(TAG, e.toString());
77            // We can return an empty config for unknown networks.
78            config = new PersistableBundle();
79        }
80
81        // Treat vendor.xml as if it were appended to the carrier config file we read.
82        XmlPullParser vendorInput = getApplicationContext().getResources().getXml(R.xml.vendor);
83        try {
84            PersistableBundle vendorConfig = readConfigFromXml(vendorInput, id);
85            config.putAll(vendorConfig);
86        }
87        catch (IOException | XmlPullParserException e) {
88            Log.e(TAG, e.toString());
89        }
90
91        return config;
92    }
93
94    /**
95     * Parses an XML document and returns a PersistableBundle.
96     *
97     * <p>This function iterates over each {@code <carrier_config>} node in the XML document and
98     * parses it into a bundle if its filters match {@code id}. The format of XML bundles is defined
99     * by {@link PersistableBundle#restoreFromXml}. All the matching bundles will be flattened and
100     * returned as a single bundle.</p>
101     *
102     * <p>Here is an example document. The second bundle will be applied to the first only if the
103     * GID1 is ABCD.
104     * <pre>{@code
105     * <carrier_config_list>
106     *     <carrier_config>
107     *         <boolean name="voicemail_notification_persistent_bool" value="true" />
108     *     </carrier_config>
109     *     <carrier_config gid1="ABCD">
110     *         <boolean name="voicemail_notification_persistent_bool" value="false" />
111     *     </carrier_config>
112     * </carrier_config_list>
113     * }</pre></p>
114     *
115     * @param parser an XmlPullParser pointing at the beginning of the document.
116     * @param id the details of the SIM operator used to filter parts of the document
117     * @return a possibly empty PersistableBundle containing the config values.
118     */
119    static PersistableBundle readConfigFromXml(XmlPullParser parser, CarrierIdentifier id)
120            throws IOException, XmlPullParserException {
121        PersistableBundle config = new PersistableBundle();
122
123        if (parser == null) {
124          return config;
125        }
126
127        // Iterate over each <carrier_config> node in the document and add it to the returned
128        // bundle if its filters match.
129        int event;
130        while (((event = parser.next()) != XmlPullParser.END_DOCUMENT)) {
131            if (event == XmlPullParser.START_TAG && "carrier_config".equals(parser.getName())) {
132                // Skip this fragment if it has filters that don't match.
133                if (!checkFilters(parser, id)) {
134                    continue;
135                }
136                PersistableBundle configFragment = PersistableBundle.restoreFromXml(parser);
137                config.putAll(configFragment);
138            }
139        }
140
141        return config;
142    }
143
144    /**
145     * Checks to see if an XML node matches carrier filters.
146     *
147     * <p>This iterates over the attributes of the current tag pointed to by {@code parser} and
148     * checks each one against {@code id} or {@link Build.DEVICE}. Attributes that are not specified
149     * in the node will not be checked, so a node with no attributes will always return true. The
150     * supported filter attributes are,
151     * <ul>
152     *   <li>mcc: {@link CarrierIdentifier#getMcc}</li>
153     *   <li>mnc: {@link CarrierIdentifier#getMnc}</li>
154     *   <li>gid1: {@link CarrierIdentifier#getGid1}</li>
155     *   <li>gid2: {@link CarrierIdentifier#getGid2}</li>
156     *   <li>spn: {@link CarrierIdentifier#getSpn}</li>
157     *   <li>device: {@link Build.DEVICE}</li>
158     * </ul>
159     * </p>
160     *
161     * @param parser an XmlPullParser pointing at a START_TAG with the attributes to check.
162     * @param id the carrier details to check against.
163     * @return false if any XML attribute does not match the corresponding value.
164     */
165    static boolean checkFilters(XmlPullParser parser, CarrierIdentifier id) {
166        boolean result = true;
167        for (int i = 0; i < parser.getAttributeCount(); ++i) {
168            String attribute = parser.getAttributeName(i);
169            String value = parser.getAttributeValue(i);
170            switch (attribute) {
171                case "mcc":
172                    result = result && value.equals(id.getMcc());
173                    break;
174                case "mnc":
175                    result = result && value.equals(id.getMnc());
176                    break;
177                case "gid1":
178                    result = result && value.equals(id.getGid1());
179                    break;
180                case "gid2":
181                    result = result && value.equals(id.getGid2());
182                    break;
183                case "spn":
184                    result = result && value.equals(id.getSpn());
185                    break;
186                case "device":
187                    result = result && value.equals(Build.DEVICE);
188                    break;
189                default:
190                    Log.e(TAG, "Unknown attribute " + attribute + "=" + value);
191                    result = false;
192                    break;
193            }
194        }
195        return result;
196    }
197}
198