From 981a3e9d384f7c12d1847b1922b1f26115b0f4fd Mon Sep 17 00:00:00 2001 From: twyen Date: Wed, 7 Feb 2018 17:32:54 -0800 Subject: Support GID1 for VVM configs This CL allows VVM configs to be specifed with Group identifier, which takes precedence over configs with only MCC/MNC. TelephonyVvmConfigManger is renamed to DialerVvmConfigManger. It was created when VVM was still in telephony, and need to separate itself from the CarrierConfig app. Bug: 72666573 Test: Unit tests PiperOrigin-RevId: 184924155 Change-Id: Ic71e99ed2b3015eed87dfb7e111538ff3b744206 --- .../android/voicemail/impl/CarrierIdentifier.java | 71 +++++++ .../voicemail/impl/CarrierIdentifierMatcher.java | 60 ++++++ .../voicemail/impl/DialerVvmConfigManager.java | 230 +++++++++++++++++++++ .../voicemail/impl/OmtpVvmCarrierConfigHelper.java | 16 +- .../voicemail/impl/TelephonyVvmConfigManager.java | 160 -------------- 5 files changed, 370 insertions(+), 167 deletions(-) create mode 100644 java/com/android/voicemail/impl/CarrierIdentifier.java create mode 100644 java/com/android/voicemail/impl/CarrierIdentifierMatcher.java create mode 100644 java/com/android/voicemail/impl/DialerVvmConfigManager.java delete mode 100644 java/com/android/voicemail/impl/TelephonyVvmConfigManager.java (limited to 'java/com/android/voicemail') diff --git a/java/com/android/voicemail/impl/CarrierIdentifier.java b/java/com/android/voicemail/impl/CarrierIdentifier.java new file mode 100644 index 000000000..82b6a2440 --- /dev/null +++ b/java/com/android/voicemail/impl/CarrierIdentifier.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.voicemail.impl; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build.VERSION_CODES; +import android.telecom.PhoneAccountHandle; +import android.telephony.TelephonyManager; +import com.google.auto.value.AutoValue; + +/** Identifies a carrier. */ +@AutoValue +@TargetApi(VERSION_CODES.O) +@SuppressWarnings("missingpermission") +public abstract class CarrierIdentifier { + + public abstract String mccMnc(); + + /** + * Group ID Level 1. Used to identify MVNO(Mobile Virtual Network Operators) who subleases other + * carrier's network and share their mccMnc. MVNO should have a GID1 different from the host. + */ + public abstract String gid1(); + + /** Builder for the matcher */ + @AutoValue.Builder + public abstract static class Builder { + + public abstract Builder setMccMnc(String mccMnc); + + public abstract Builder setGid1(String gid1); + + public abstract CarrierIdentifier build(); + } + + public static Builder builder() { + return new AutoValue_CarrierIdentifier.Builder().setGid1(""); + } + + public static CarrierIdentifier forHandle( + Context context, PhoneAccountHandle phoneAccountHandle) { + TelephonyManager telephonyManager = + context + .getSystemService(TelephonyManager.class) + .createForPhoneAccountHandle(phoneAccountHandle); + if (telephonyManager == null) { + throw new IllegalArgumentException("Invalid PhoneAccountHandle"); + } + String gid1 = telephonyManager.getGroupIdLevel1(); + if (gid1 == null) { + gid1 = ""; + } + + return builder().setMccMnc(telephonyManager.getSimOperator()).setGid1(gid1).build(); + } +} diff --git a/java/com/android/voicemail/impl/CarrierIdentifierMatcher.java b/java/com/android/voicemail/impl/CarrierIdentifierMatcher.java new file mode 100644 index 000000000..d7c28fe77 --- /dev/null +++ b/java/com/android/voicemail/impl/CarrierIdentifierMatcher.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.voicemail.impl; + +import com.google.auto.value.AutoValue; +import com.google.common.base.Optional; + +/** + * Matches a {@link CarrierIdentifier}. Full equality check on CarrierIdentifiers is often unfit + * because non-MVNO carriers usually just specify the {@link CarrierIdentifier#mccMnc()} while their + * {@link CarrierIdentifier#gid1()} could be anything. This matcher ignore fields that are not + * specified in the matcher. + */ +@AutoValue +public abstract class CarrierIdentifierMatcher { + + public abstract String mccMnc(); + + public abstract Optional gid1(); + + public static Builder builder() { + return new AutoValue_CarrierIdentifierMatcher.Builder(); + } + + /** Builder for the matcher */ + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder setMccMnc(String mccMnc); + + public abstract Builder setGid1(String gid1); + + public abstract CarrierIdentifierMatcher build(); + } + + public boolean matches(CarrierIdentifier carrierIdentifier) { + if (!mccMnc().equals(carrierIdentifier.mccMnc())) { + return false; + } + if (gid1().isPresent()) { + if (!gid1().get().equals(carrierIdentifier.gid1())) { + return false; + } + } + return true; + } +} diff --git a/java/com/android/voicemail/impl/DialerVvmConfigManager.java b/java/com/android/voicemail/impl/DialerVvmConfigManager.java new file mode 100644 index 000000000..7fa960e34 --- /dev/null +++ b/java/com/android/voicemail/impl/DialerVvmConfigManager.java @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.voicemail.impl; + +import android.content.Context; +import android.net.Uri; +import android.os.PersistableBundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; +import android.util.ArrayMap; +import com.android.dialer.configprovider.ConfigProviderBindings; +import com.android.voicemail.impl.utils.XmlUtils; +import com.google.common.collect.ComparisonChain; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Map; +import java.util.Map.Entry; +import java.util.SortedSet; +import java.util.TreeSet; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +/** Load and caches dialer vvm config from res/xml/vvm_config.xml */ +public class DialerVvmConfigManager { + private static class ConfigEntry implements Comparable { + + final CarrierIdentifierMatcher matcher; + final PersistableBundle config; + + ConfigEntry(CarrierIdentifierMatcher matcher, PersistableBundle config) { + this.matcher = matcher; + this.config = config; + } + + /** + * A more specific matcher should return a negative value to have higher priority over generic + * matchers. + */ + @Override + public int compareTo(@NonNull ConfigEntry other) { + ComparisonChain comparisonChain = ComparisonChain.start(); + if (!(matcher.gid1().isPresent() && other.matcher.gid1().isPresent())) { + if (matcher.gid1().isPresent()) { + return -1; + } else if (other.matcher.gid1().isPresent()) { + return 1; + } else { + return 0; + } + } else { + comparisonChain = comparisonChain.compare(matcher.gid1().get(), other.matcher.gid1().get()); + } + + return comparisonChain.compare(matcher.mccMnc(), other.matcher.mccMnc()).result(); + } + } + + private static final String TAG_PERSISTABLEMAP = "pbundle_as_map"; + + /** + * A string array of MCCMNC the config applies to. Addtional filters should be appended as the URI + * query parameter format. + * + *

For example{@code } + * + * @see #KEY_GID1 + */ + @VisibleForTesting static final String KEY_MCCMNC = "mccmnc"; + + /** + * Additional query parameter in {@link #KEY_MCCMNC} to filter by the Group ID level 1. + * + * @see CarrierIdentifierMatcher#gid1() + */ + private static final String KEY_GID1 = "gid1"; + + private static final String KEY_FEATURE_FLAG_NAME = "feature_flag_name"; + + private static Map> cachedConfigs; + + private final Map> configs; + + public DialerVvmConfigManager(Context context) { + if (cachedConfigs == null) { + cachedConfigs = loadConfigs(context, context.getResources().getXml(R.xml.vvm_config)); + } + configs = cachedConfigs; + } + + @VisibleForTesting + DialerVvmConfigManager(Context context, XmlPullParser parser) { + configs = loadConfigs(context, parser); + } + + @Nullable + public PersistableBundle getConfig(CarrierIdentifier carrierIdentifier) { + if (!configs.containsKey(carrierIdentifier.mccMnc())) { + return null; + } + for (ConfigEntry configEntry : configs.get(carrierIdentifier.mccMnc())) { + if (configEntry.matcher.matches(carrierIdentifier)) { + return configEntry.config; + } + } + return null; + } + + private static Map> loadConfigs( + Context context, XmlPullParser parser) { + Map> configs = new ArrayMap<>(); + try { + ArrayList list = readBundleList(parser); + for (Object object : list) { + if (!(object instanceof PersistableBundle)) { + throw new IllegalArgumentException("PersistableBundle expected, got " + object); + } + PersistableBundle bundle = (PersistableBundle) object; + + if (bundle.containsKey(KEY_FEATURE_FLAG_NAME) + && !ConfigProviderBindings.get(context) + .getBoolean(bundle.getString(KEY_FEATURE_FLAG_NAME), false)) { + continue; + } + + String[] identifiers = bundle.getStringArray(KEY_MCCMNC); + if (identifiers == null) { + throw new IllegalArgumentException("MCCMNC is null"); + } + for (String identifier : identifiers) { + Uri uri = Uri.parse(identifier); + String mccMnc = uri.getPath(); + SortedSet set; + if (configs.containsKey(mccMnc)) { + set = configs.get(mccMnc); + } else { + // Need a SortedSet so matchers will be sorted by priority. + set = new TreeSet<>(); + configs.put(mccMnc, set); + } + CarrierIdentifierMatcher.Builder matcherBuilder = CarrierIdentifierMatcher.builder(); + matcherBuilder.setMccMnc(mccMnc); + if (uri.getQueryParameterNames().contains(KEY_GID1)) { + matcherBuilder.setGid1(uri.getQueryParameter(KEY_GID1)); + } + set.add(new ConfigEntry(matcherBuilder.build(), bundle)); + } + } + } catch (IOException | XmlPullParserException e) { + throw new RuntimeException(e); + } + return configs; + } + + @Nullable + public static ArrayList readBundleList(XmlPullParser in) + throws IOException, XmlPullParserException { + final int outerDepth = in.getDepth(); + int event; + while (((event = in.next()) != XmlPullParser.END_DOCUMENT) + && (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) { + if (event == XmlPullParser.START_TAG) { + final String startTag = in.getName(); + final String[] tagName = new String[1]; + in.next(); + return XmlUtils.readThisListXml(in, startTag, tagName, new MyReadMapCallback(), false); + } + } + return null; + } + + public static PersistableBundle restoreFromXml(XmlPullParser in) + throws IOException, XmlPullParserException { + final int outerDepth = in.getDepth(); + final String startTag = in.getName(); + final String[] tagName = new String[1]; + int event; + while (((event = in.next()) != XmlPullParser.END_DOCUMENT) + && (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) { + if (event == XmlPullParser.START_TAG) { + ArrayMap map = + XmlUtils.readThisArrayMapXml(in, startTag, tagName, new MyReadMapCallback()); + PersistableBundle result = new PersistableBundle(); + for (Entry entry : map.entrySet()) { + Object value = entry.getValue(); + if (value instanceof Integer) { + result.putInt(entry.getKey(), (int) value); + } else if (value instanceof Boolean) { + result.putBoolean(entry.getKey(), (boolean) value); + } else if (value instanceof String) { + result.putString(entry.getKey(), (String) value); + } else if (value instanceof String[]) { + result.putStringArray(entry.getKey(), (String[]) value); + } else if (value instanceof PersistableBundle) { + result.putPersistableBundle(entry.getKey(), (PersistableBundle) value); + } + } + return result; + } + } + return PersistableBundle.EMPTY; + } + + static class MyReadMapCallback implements XmlUtils.ReadMapCallback { + + @Override + public Object readThisUnknownObjectXml(XmlPullParser in, String tag) + throws XmlPullParserException, IOException { + if (TAG_PERSISTABLEMAP.equals(tag)) { + return restoreFromXml(in); + } + throw new XmlPullParserException("Unknown tag=" + tag); + } + } +} diff --git a/java/com/android/voicemail/impl/OmtpVvmCarrierConfigHelper.java b/java/com/android/voicemail/impl/OmtpVvmCarrierConfigHelper.java index ef62d2a75..f8a9e4bcf 100644 --- a/java/com/android/voicemail/impl/OmtpVvmCarrierConfigHelper.java +++ b/java/com/android/voicemail/impl/OmtpVvmCarrierConfigHelper.java @@ -55,6 +55,7 @@ import java.util.Set; *

TODO(twyen): refactor this to an interface. */ @TargetApi(VERSION_CODES.O) +@SuppressWarnings("missingpermission") public class OmtpVvmCarrierConfigHelper { private static final String TAG = "OmtpVvmCarrierCfgHlpr"; @@ -131,7 +132,8 @@ public class OmtpVvmCarrierConfigHelper { carrierConfig = getCarrierConfig(telephonyManager); telephonyConfig = - new TelephonyVvmConfigManager(context).getConfig(telephonyManager.getSimOperator()); + new DialerVvmConfigManager(context) + .getConfig(CarrierIdentifier.forHandle(context, phoneAccountHandle)); } vvmType = getVvmType(); @@ -198,12 +200,6 @@ public class OmtpVvmCarrierConfigHelper { return (String) getValue(key); } - @Nullable - public Set getCarrierVvmPackageNames() { - Assert.checkArgument(isValid()); - return getCarrierVvmPackageNamesWithoutValidation(); - } - @Nullable private Set getCarrierVvmPackageNamesWithoutValidation() { Set names = getCarrierVvmPackageNames(overrideConfig); @@ -217,6 +213,12 @@ public class OmtpVvmCarrierConfigHelper { return getCarrierVvmPackageNames(telephonyConfig); } + @Nullable + public Set getCarrierVvmPackageNames() { + Assert.checkArgument(isValid()); + return getCarrierVvmPackageNamesWithoutValidation(); + } + private static Set getCarrierVvmPackageNames(@Nullable PersistableBundle bundle) { if (bundle == null) { return null; diff --git a/java/com/android/voicemail/impl/TelephonyVvmConfigManager.java b/java/com/android/voicemail/impl/TelephonyVvmConfigManager.java deleted file mode 100644 index ecf4e6ffc..000000000 --- a/java/com/android/voicemail/impl/TelephonyVvmConfigManager.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.voicemail.impl; - -import android.content.Context; -import android.os.PersistableBundle; -import android.support.annotation.Nullable; -import android.support.annotation.VisibleForTesting; -import android.util.ArrayMap; -import com.android.dialer.configprovider.ConfigProviderBindings; -import com.android.voicemail.impl.utils.XmlUtils; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Map; -import java.util.Map.Entry; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -/** Load and caches telephony vvm config from res/xml/vvm_config.xml */ -public class TelephonyVvmConfigManager { - - private static final String TAG = "TelephonyVvmCfgMgr"; - - private static final boolean USE_DEBUG_CONFIG = false; - - private static final String TAG_PERSISTABLEMAP = "pbundle_as_map"; - - @VisibleForTesting static final String KEY_MCCMNC = "mccmnc"; - - private static final String KEY_FEATURE_FLAG_NAME = "feature_flag_name"; - - private static Map cachedConfigs; - - private final Map configs; - - public TelephonyVvmConfigManager(Context context) { - if (cachedConfigs == null) { - cachedConfigs = loadConfigs(context, context.getResources().getXml(R.xml.vvm_config)); - } - configs = cachedConfigs; - } - - @VisibleForTesting - TelephonyVvmConfigManager(Context context, XmlPullParser parser) { - configs = loadConfigs(context, parser); - } - - @Nullable - public PersistableBundle getConfig(String mccMnc) { - if (USE_DEBUG_CONFIG) { - return configs.get("TEST"); - } - return configs.get(mccMnc); - } - - private static Map loadConfigs(Context context, XmlPullParser parser) { - Map configs = new ArrayMap<>(); - try { - ArrayList list = readBundleList(parser); - for (Object object : list) { - if (!(object instanceof PersistableBundle)) { - throw new IllegalArgumentException("PersistableBundle expected, got " + object); - } - PersistableBundle bundle = (PersistableBundle) object; - - if (bundle.containsKey(KEY_FEATURE_FLAG_NAME) - && !ConfigProviderBindings.get(context) - .getBoolean(bundle.getString(KEY_FEATURE_FLAG_NAME), false)) { - continue; - } - - String[] mccMncs = bundle.getStringArray(KEY_MCCMNC); - if (mccMncs == null) { - throw new IllegalArgumentException("MCCMNC is null"); - } - for (String mccMnc : mccMncs) { - configs.put(mccMnc, bundle); - } - } - } catch (IOException | XmlPullParserException e) { - throw new RuntimeException(e); - } - return configs; - } - - @Nullable - public static ArrayList readBundleList(XmlPullParser in) - throws IOException, XmlPullParserException { - final int outerDepth = in.getDepth(); - int event; - while (((event = in.next()) != XmlPullParser.END_DOCUMENT) - && (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) { - if (event == XmlPullParser.START_TAG) { - final String startTag = in.getName(); - final String[] tagName = new String[1]; - in.next(); - return XmlUtils.readThisListXml(in, startTag, tagName, new MyReadMapCallback(), false); - } - } - return null; - } - - public static PersistableBundle restoreFromXml(XmlPullParser in) - throws IOException, XmlPullParserException { - final int outerDepth = in.getDepth(); - final String startTag = in.getName(); - final String[] tagName = new String[1]; - int event; - while (((event = in.next()) != XmlPullParser.END_DOCUMENT) - && (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) { - if (event == XmlPullParser.START_TAG) { - ArrayMap map = - XmlUtils.readThisArrayMapXml(in, startTag, tagName, new MyReadMapCallback()); - PersistableBundle result = new PersistableBundle(); - for (Entry entry : map.entrySet()) { - Object value = entry.getValue(); - if (value instanceof Integer) { - result.putInt(entry.getKey(), (int) value); - } else if (value instanceof Boolean) { - result.putBoolean(entry.getKey(), (boolean) value); - } else if (value instanceof String) { - result.putString(entry.getKey(), (String) value); - } else if (value instanceof String[]) { - result.putStringArray(entry.getKey(), (String[]) value); - } else if (value instanceof PersistableBundle) { - result.putPersistableBundle(entry.getKey(), (PersistableBundle) value); - } - } - return result; - } - } - return PersistableBundle.EMPTY; - } - - static class MyReadMapCallback implements XmlUtils.ReadMapCallback { - - @Override - public Object readThisUnknownObjectXml(XmlPullParser in, String tag) - throws XmlPullParserException, IOException { - if (TAG_PERSISTABLEMAP.equals(tag)) { - return restoreFromXml(in); - } - throw new XmlPullParserException("Unknown tag=" + tag); - } - } -} -- cgit v1.2.3