1/*
2 * Copyright (C) 2016 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
17package com.android.voicemail.impl;
18
19import android.content.Context;
20import android.os.PersistableBundle;
21import android.support.annotation.Nullable;
22import android.support.annotation.VisibleForTesting;
23import android.util.ArrayMap;
24import com.android.dialer.common.ConfigProviderBindings;
25import com.android.voicemail.impl.utils.XmlUtils;
26import java.io.IOException;
27import java.util.ArrayList;
28import java.util.Map;
29import java.util.Map.Entry;
30import org.xmlpull.v1.XmlPullParser;
31import org.xmlpull.v1.XmlPullParserException;
32
33/** Load and caches telephony vvm config from res/xml/vvm_config.xml */
34public class TelephonyVvmConfigManager {
35
36  private static final String TAG = "TelephonyVvmCfgMgr";
37
38  private static final boolean USE_DEBUG_CONFIG = false;
39
40  private static final String TAG_PERSISTABLEMAP = "pbundle_as_map";
41
42  @VisibleForTesting static final String KEY_MCCMNC = "mccmnc";
43
44  private static final String KEY_FEATURE_FLAG_NAME = "feature_flag_name";
45
46  private static Map<String, PersistableBundle> sCachedConfigs;
47
48  private final Map<String, PersistableBundle> mConfigs;
49
50  public TelephonyVvmConfigManager(Context context) {
51    if (sCachedConfigs == null) {
52      sCachedConfigs = loadConfigs(context, context.getResources().getXml(R.xml.vvm_config));
53    }
54    mConfigs = sCachedConfigs;
55  }
56
57  @VisibleForTesting
58  TelephonyVvmConfigManager(Context context, XmlPullParser parser) {
59    mConfigs = loadConfigs(context, parser);
60  }
61
62  @Nullable
63  public PersistableBundle getConfig(String mccMnc) {
64    if (USE_DEBUG_CONFIG) {
65      return mConfigs.get("TEST");
66    }
67    return mConfigs.get(mccMnc);
68  }
69
70  private static Map<String, PersistableBundle> loadConfigs(Context context, XmlPullParser parser) {
71    Map<String, PersistableBundle> configs = new ArrayMap<>();
72    try {
73      ArrayList list = readBundleList(parser);
74      for (Object object : list) {
75        if (!(object instanceof PersistableBundle)) {
76          throw new IllegalArgumentException("PersistableBundle expected, got " + object);
77        }
78        PersistableBundle bundle = (PersistableBundle) object;
79
80        if (bundle.containsKey(KEY_FEATURE_FLAG_NAME)
81            && !ConfigProviderBindings.get(context)
82                .getBoolean(bundle.getString(KEY_FEATURE_FLAG_NAME), false)) {
83          continue;
84        }
85
86        String[] mccMncs = bundle.getStringArray(KEY_MCCMNC);
87        if (mccMncs == null) {
88          throw new IllegalArgumentException("MCCMNC is null");
89        }
90        for (String mccMnc : mccMncs) {
91          configs.put(mccMnc, bundle);
92        }
93      }
94    } catch (IOException | XmlPullParserException e) {
95      throw new RuntimeException(e);
96    }
97    return configs;
98  }
99
100  @Nullable
101  public static ArrayList readBundleList(XmlPullParser in)
102      throws IOException, XmlPullParserException {
103    final int outerDepth = in.getDepth();
104    int event;
105    while (((event = in.next()) != XmlPullParser.END_DOCUMENT)
106        && (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
107      if (event == XmlPullParser.START_TAG) {
108        final String startTag = in.getName();
109        final String[] tagName = new String[1];
110        in.next();
111        return XmlUtils.readThisListXml(in, startTag, tagName, new MyReadMapCallback(), false);
112      }
113    }
114    return null;
115  }
116
117  public static PersistableBundle restoreFromXml(XmlPullParser in)
118      throws IOException, XmlPullParserException {
119    final int outerDepth = in.getDepth();
120    final String startTag = in.getName();
121    final String[] tagName = new String[1];
122    int event;
123    while (((event = in.next()) != XmlPullParser.END_DOCUMENT)
124        && (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
125      if (event == XmlPullParser.START_TAG) {
126        ArrayMap<String, ?> map =
127            XmlUtils.readThisArrayMapXml(in, startTag, tagName, new MyReadMapCallback());
128        PersistableBundle result = new PersistableBundle();
129        for (Entry<String, ?> entry : map.entrySet()) {
130          Object value = entry.getValue();
131          if (value instanceof Integer) {
132            result.putInt(entry.getKey(), (int) value);
133          } else if (value instanceof Boolean) {
134            result.putBoolean(entry.getKey(), (boolean) value);
135          } else if (value instanceof String) {
136            result.putString(entry.getKey(), (String) value);
137          } else if (value instanceof String[]) {
138            result.putStringArray(entry.getKey(), (String[]) value);
139          } else if (value instanceof PersistableBundle) {
140            result.putPersistableBundle(entry.getKey(), (PersistableBundle) value);
141          }
142        }
143        return result;
144      }
145    }
146    return PersistableBundle.EMPTY;
147  }
148
149  static class MyReadMapCallback implements XmlUtils.ReadMapCallback {
150
151    @Override
152    public Object readThisUnknownObjectXml(XmlPullParser in, String tag)
153        throws XmlPullParserException, IOException {
154      if (TAG_PERSISTABLEMAP.equals(tag)) {
155        return restoreFromXml(in);
156      }
157      throw new XmlPullParserException("Unknown tag=" + tag);
158    }
159  }
160}
161