summaryrefslogtreecommitdiffstats
path: root/src/com/android/carrierconfig/DefaultCarrierConfigService.java
blob: 298033253a995b5be036e275e411687eec2ea53b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
package com.android.carrierconfig;

import android.annotation.Nullable;
import android.os.Build;
import android.os.PersistableBundle;
import android.service.carrier.CarrierIdentifier;
import android.service.carrier.CarrierService;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;

import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Provides network overrides for carrier configuration.
 *
 * The configuration available through CarrierConfigManager is a combination of default values,
 * default network overrides, and carrier overrides. The default network overrides are provided by
 * this service. For a given network, we look for a matching XML file in our assets folder, and
 * return the PersistableBundle from that file. Assets are preferred over Resources because resource
 * overlays only support using MCC+MNC and that doesn't work with MVNOs. The only resource file used
 * is vendor.xml, to provide vendor-specific overrides.
 */
public class DefaultCarrierConfigService extends CarrierService {

    private static final String SPN_EMPTY_MATCH = "null";

    private static final String CARRIER_ID_PREFIX = "carrier_config_carrierid_";

    private static final String MCCMNC_PREFIX = "carrier_config_mccmnc_";

    private static final String TAG = "DefaultCarrierConfigService";

    private XmlPullParserFactory mFactory;

    public DefaultCarrierConfigService() {
        Log.d(TAG, "Service created");
        mFactory = null;
    }

    /**
     * Returns per-network overrides for carrier configuration.
     *
     * This returns a carrier config bundle appropriate for the given carrier by reading data from
     * files in our assets folder. Config files in assets folder are carrier-id-indexed
     * {@link TelephonyManager#getSimCarrierId()}. NOTE: config files named after mccmnc
     * are for those without a matching carrier id and should be renamed to carrier id once the
     * missing IDs are added to
     * <a href="https://android.googlesource.com/platform/packages/providers/TelephonyProvider/+/master/assets/carrier_list.textpb">carrier id list</a>
     *
     * First, look for file named after
     * carrier_config_carrierid_<carrierid>_<carriername>.xml if carrier id is not
     * {@link TelephonyManager#UNKNOWN_CARRIER_ID}. Note <carriername> is to improve the
     * readability which should not be used to search asset files. If there is no configuration,
     * then we look for a file named after the MCC+MNC of {@code id} as a fallback. Last, we read
     * res/xml/vendor.xml.
     *
     * carrierid.xml doesn't support multiple bundles with filters as each carrier including MVNOs
     * has its own config file named after its carrier id.
     * Both vendor.xml and MCC+MNC.xml files may contain multiple bundles with filters on them.
     * All the matching bundles are flattened to return one carrier config bundle.
     */
    @Override
    public PersistableBundle onLoadConfig(CarrierIdentifier id) {
        Log.d(TAG, "Config being fetched");

        if (id == null) {
            return null;
        }

        PersistableBundle config = new PersistableBundle();
        try {
            synchronized (this) {
                if (mFactory == null) {
                    mFactory = XmlPullParserFactory.newInstance();
                }
            }

            XmlPullParser parser = mFactory.newPullParser();
            if (id.getCarrierId() != TelephonyManager.UNKNOWN_CARRIER_ID) {
                PersistableBundle configByCarrierId = new PersistableBundle();
                PersistableBundle configByPreciseCarrierId = new PersistableBundle();
                PersistableBundle configByMccMncFallBackCarrierId = new PersistableBundle();
                int mccmncCarrierId = TelephonyManager.from(getApplicationContext())
                        .getCarrierIdFromMccMnc(id.getMcc() + id.getMnc());
                for (String file : getApplicationContext().getAssets().list("")) {
                    if (file.startsWith(CARRIER_ID_PREFIX + id.getPreciseCarrierId() + "_")) {
                        parser.setInput(getApplicationContext().getAssets().open(file), "utf-8");
                        configByPreciseCarrierId = readConfigFromXml(parser, null);
                        break;
                    } else if (file.startsWith(CARRIER_ID_PREFIX + id.getCarrierId() + "_")) {
                        parser.setInput(getApplicationContext().getAssets().open(file), "utf-8");
                        configByCarrierId = readConfigFromXml(parser, null);
                    } else if (file.startsWith(CARRIER_ID_PREFIX + mccmncCarrierId + "_")) {
                        parser.setInput(getApplicationContext().getAssets().open(file), "utf-8");
                        configByMccMncFallBackCarrierId = readConfigFromXml(parser, null);
                    }
                }

                // priority: precise carrier id > carrier id > mccmnc fallback carrier id
                if (!configByPreciseCarrierId.isEmpty()) {
                    config = configByPreciseCarrierId;
                } else if (!configByCarrierId.isEmpty()) {
                    config = configByCarrierId;
                } else if (!configByMccMncFallBackCarrierId.isEmpty()) {
                    config = configByMccMncFallBackCarrierId;
                }
            }
            if (config.isEmpty()) {
                // fallback to use mccmnc.xml when there is no carrier id named configuration found.
                parser.setInput(getApplicationContext().getAssets().open(
                        MCCMNC_PREFIX + id.getMcc() + id.getMnc() + ".xml"), "utf-8");
                config = readConfigFromXml(parser, id);
            }

        }
        catch (IOException | XmlPullParserException e) {
            Log.d(TAG, e.toString());
            // We can return an empty config for unknown networks.
            config = new PersistableBundle();
        }

        // Treat vendor.xml as if it were appended to the carrier config file we read.
        XmlPullParser vendorInput = getApplicationContext().getResources().getXml(R.xml.vendor);
        try {
            PersistableBundle vendorConfig = readConfigFromXml(vendorInput, id);
            config.putAll(vendorConfig);
        }
        catch (IOException | XmlPullParserException e) {
            Log.e(TAG, e.toString());
        }

        return config;
    }

    /**
     * Parses an XML document and returns a PersistableBundle.
     *
     * <p>This function iterates over each {@code <carrier_config>} node in the XML document and
     * parses it into a bundle if its filters match {@code id}. XML documents named after carrier id
     * doesn't support filter match as each carrier including MVNOs will have its own config file.
     * The format of XML bundles is defined
     * by {@link PersistableBundle#restoreFromXml}. All the matching bundles will be flattened and
     * returned as a single bundle.</p>
     *
     * <p>Here is an example document in vendor.xml.
     * <pre>{@code
     * <carrier_config_list>
     *     <carrier_config cid="1938" name="verizon">
     *         <boolean name="voicemail_notification_persistent_bool" value="true" />
     *     </carrier_config>
     *     <carrier_config cid="1788" name="sprint">
     *         <boolean name="voicemail_notification_persistent_bool" value="false" />
     *     </carrier_config>
     * </carrier_config_list>
     * }</pre></p>
     *
     * <p>Here is an example document. The second bundle will be applied to the first only if the
     * GID1 is ABCD.
     * <pre>{@code
     * <carrier_config_list>
     *     <carrier_config>
     *         <boolean name="voicemail_notification_persistent_bool" value="true" />
     *     </carrier_config>
     *     <carrier_config gid1="ABCD">
     *         <boolean name="voicemail_notification_persistent_bool" value="false" />
     *     </carrier_config>
     * </carrier_config_list>
     * }</pre></p>
     *
     * @param parser an XmlPullParser pointing at the beginning of the document.
     * @param id the details of the SIM operator used to filter parts of the document. If read from
     *           files named after carrier id, this will be set to {@null code} as no filter match
     *           needed.
     * @return a possibly empty PersistableBundle containing the config values.
     */
    static PersistableBundle readConfigFromXml(XmlPullParser parser, @Nullable CarrierIdentifier id)
            throws IOException, XmlPullParserException {
        PersistableBundle config = new PersistableBundle();

        if (parser == null) {
          return config;
        }

        // Iterate over each <carrier_config> node in the document and add it to the returned
        // bundle if its filters match.
        int event;
        while (((event = parser.next()) != XmlPullParser.END_DOCUMENT)) {
            if (event == XmlPullParser.START_TAG && "carrier_config".equals(parser.getName())) {
                // Skip this fragment if it has filters that don't match.
                if (id != null && !checkFilters(parser, id)) {
                    continue;
                }
                PersistableBundle configFragment = PersistableBundle.restoreFromXml(parser);
                config.putAll(configFragment);
            }
        }

        return config;
    }

    /**
     * Checks to see if an XML node matches carrier filters.
     *
     * <p>This iterates over the attributes of the current tag pointed to by {@code parser} and
     * checks each one against {@code id} or {@link Build.DEVICE}. Attributes that are not specified
     * in the node will not be checked, so a node with no attributes will always return true. The
     * supported filter attributes are,
     * <ul>
     *   <li>mcc: {@link CarrierIdentifier#getMcc}</li>
     *   <li>mnc: {@link CarrierIdentifier#getMnc}</li>
     *   <li>gid1: {@link CarrierIdentifier#getGid1}</li>
     *   <li>gid2: {@link CarrierIdentifier#getGid2}</li>
     *   <li>spn: {@link CarrierIdentifier#getSpn}</li>
     *   <li>imsi: {@link CarrierIdentifier#getImsi}</li>
     *   <li>device: {@link Build.DEVICE}</li>
     *   <li>cid: {@link CarrierIdentifier#getCarrierId()}
     *   or {@link CarrierIdentifier#getPreciseCarrierId()}</li>
     * </ul>
     * </p>
     *
     * <p>
     * The attributes imsi and spn can be expressed as regexp to filter on patterns.
     * The spn attribute can be set to the string "null" to allow matching against a SIM
     * with no spn set.
     * </p>
     *
     * @param parser an XmlPullParser pointing at a START_TAG with the attributes to check.
     * @param id the carrier details to check against.
     * @return false if any XML attribute does not match the corresponding value.
     */
    static boolean checkFilters(XmlPullParser parser, CarrierIdentifier id) {
        boolean result = true;
        for (int i = 0; i < parser.getAttributeCount(); ++i) {
            String attribute = parser.getAttributeName(i);
            String value = parser.getAttributeValue(i);
            switch (attribute) {
                case "mcc":
                    result = result && value.equals(id.getMcc());
                    break;
                case "mnc":
                    result = result && value.equals(id.getMnc());
                    break;
                case "gid1":
                    result = result && value.equalsIgnoreCase(id.getGid1());
                    break;
                case "gid2":
                    result = result && value.equalsIgnoreCase(id.getGid2());
                    break;
                case "spn":
                    result = result && matchOnSP(value, id);
                    break;
                case "imsi":
                    result = result && matchOnImsi(value, id);
                    break;
                case "device":
                    result = result && value.equalsIgnoreCase(Build.DEVICE);
                    break;
                case "cid":
                    result = result && (value.equals(id.getCarrierId())
                            || value.equals(id.getPreciseCarrierId()));
                    break;
                case "name":
                    // name is used together with cid for readability. ignore for filter.
                    break;
                default:
                    Log.e(TAG, "Unknown attribute " + attribute + "=" + value);
                    result = false;
                    break;
            }
        }
        return result;
    }

    /**
     * Check to see if the IMSI expression from the XML matches the IMSI of the
     * Carrier.
     *
     * @param xmlImsi IMSI expression fetched from the resource XML
     * @param id Id of the evaluated CarrierIdentifier
     * @return true if the XML IMSI matches the IMSI of CarrierIdentifier, false
     *         otherwise.
     */
    static boolean matchOnImsi(String xmlImsi, CarrierIdentifier id) {
        boolean matchFound = false;

        String currentImsi = id.getImsi();
        // If we were able to retrieve current IMSI, see if it matches.
        if (currentImsi != null) {
            Pattern imsiPattern = Pattern.compile(xmlImsi, Pattern.CASE_INSENSITIVE);
            Matcher matcher = imsiPattern.matcher(currentImsi);
            matchFound = matcher.matches();
        }
        return matchFound;
    }

    /**
     * Check to see if the service provider name expression from the XML matches the
     * CarrierIdentifier.
     *
     * @param xmlSP SP expression fetched from the resource XML
     * @param id Id of the evaluated CarrierIdentifier
     * @return true if the XML SP matches the phone's SP, false otherwise.
     */
    static boolean matchOnSP(String xmlSP, CarrierIdentifier id) {
        boolean matchFound = false;

        String currentSP = id.getSpn();
        if (SPN_EMPTY_MATCH.equalsIgnoreCase(xmlSP)) {
            if (TextUtils.isEmpty(currentSP)) {
                matchFound = true;
            }
        } else if (currentSP != null) {
            Pattern spPattern = Pattern.compile(xmlSP, Pattern.CASE_INSENSITIVE);
            Matcher matcher = spPattern.matcher(currentSP);
            matchFound = matcher.matches();
        }
        return matchFound;
    }
}