/* * Copyright (C) 2015 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.app.PendingIntent; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.PersistableBundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.telecom.PhoneAccountHandle; import android.telephony.CarrierConfigManager; import android.telephony.TelephonyManager; import android.telephony.VisualVoicemailSmsFilterSettings; import android.text.TextUtils; import android.util.ArraySet; import com.android.dialer.common.Assert; import com.android.voicemail.impl.configui.ConfigOverrideFragment; import com.android.voicemail.impl.protocol.VisualVoicemailProtocol; import com.android.voicemail.impl.protocol.VisualVoicemailProtocolFactory; import com.android.voicemail.impl.sms.StatusMessage; import com.android.voicemail.impl.sync.VvmAccountManager; import java.util.Collections; import java.util.Optional; import java.util.Set; /** * Manages carrier dependent visual voicemail configuration values. The primary source is the value * retrieved from CarrierConfigManager. If CarrierConfigManager does not provide the config * (KEY_VVM_TYPE_STRING is empty, or "hidden" configs), then the value hardcoded in telephony will * be used (in res/xml/vvm_config.xml) * *

Hidden configs are new configs that are planned for future APIs, or miscellaneous settings * that may clutter CarrierConfigManager too much. * *

The current hidden configs are: {@link #getSslPort()} {@link #getDisabledCapabilities()} * *

TODO(twyen): refactor this to an interface. */ @TargetApi(VERSION_CODES.O) @SuppressWarnings({"missingpermission"}) public class OmtpVvmCarrierConfigHelper { private static final String TAG = "OmtpVvmCarrierCfgHlpr"; public static final String KEY_VVM_TYPE_STRING = CarrierConfigManager.KEY_VVM_TYPE_STRING; public static final String KEY_VVM_DESTINATION_NUMBER_STRING = CarrierConfigManager.KEY_VVM_DESTINATION_NUMBER_STRING; public static final String KEY_VVM_PORT_NUMBER_INT = CarrierConfigManager.KEY_VVM_PORT_NUMBER_INT; public static final String KEY_CARRIER_VVM_PACKAGE_NAME_STRING = CarrierConfigManager.KEY_CARRIER_VVM_PACKAGE_NAME_STRING; public static final String KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY = "carrier_vvm_package_name_string_array"; public static final String KEY_VVM_PREFETCH_BOOL = CarrierConfigManager.KEY_VVM_PREFETCH_BOOL; public static final String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL = CarrierConfigManager.KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL; /** @see #getSslPort() */ public static final String KEY_VVM_SSL_PORT_NUMBER_INT = "vvm_ssl_port_number_int"; /** @see #isLegacyModeEnabled() */ public static final String KEY_VVM_LEGACY_MODE_ENABLED_BOOL = "vvm_legacy_mode_enabled_bool"; /** * Ban a capability reported by the server from being used. The array of string should be a subset * of the capabilities returned IMAP CAPABILITY command. * * @see #getDisabledCapabilities() */ public static final String KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY = "vvm_disabled_capabilities_string_array"; public static final String KEY_VVM_CLIENT_PREFIX_STRING = "vvm_client_prefix_string"; private static final String KEY_IGNORE_TRANSCRIPTION_BOOL = "vvm_ignore_transcription"; @Nullable private static PersistableBundle overrideConfigForTest; private final Context context; private final PersistableBundle carrierConfig; private final String vvmType; private final VisualVoicemailProtocol protocol; private final PersistableBundle telephonyConfig; @Nullable private final PersistableBundle overrideConfig; private PhoneAccountHandle phoneAccountHandle; public OmtpVvmCarrierConfigHelper(Context context, @Nullable PhoneAccountHandle handle) { this.context = context; phoneAccountHandle = handle; if (overrideConfigForTest != null) { overrideConfig = overrideConfigForTest; carrierConfig = new PersistableBundle(); telephonyConfig = new PersistableBundle(); } else { Optional carrierIdentifier = CarrierIdentifier.forHandle(context, handle); TelephonyManager telephonyManager = context .getSystemService(TelephonyManager.class) .createForPhoneAccountHandle(phoneAccountHandle); if (telephonyManager == null || !carrierIdentifier.isPresent()) { VvmLog.e(TAG, "PhoneAccountHandle is invalid"); carrierConfig = null; telephonyConfig = null; overrideConfig = null; vvmType = null; protocol = null; return; } if (ConfigOverrideFragment.isOverridden(context)) { overrideConfig = ConfigOverrideFragment.getConfig(context); VvmLog.w(TAG, "Config override is activated: " + overrideConfig); } else { overrideConfig = null; } carrierConfig = getCarrierConfig(telephonyManager); telephonyConfig = new DialerVvmConfigManager(context).getConfig(carrierIdentifier.get()); } vvmType = getVvmType(); protocol = VisualVoicemailProtocolFactory.create(this.context.getResources(), vvmType); } @VisibleForTesting OmtpVvmCarrierConfigHelper( Context context, PersistableBundle carrierConfig, PersistableBundle telephonyConfig, @Nullable PhoneAccountHandle phoneAccountHandle) { this.context = context; this.carrierConfig = carrierConfig; this.telephonyConfig = telephonyConfig; this.phoneAccountHandle = phoneAccountHandle; overrideConfig = null; vvmType = getVvmType(); protocol = VisualVoicemailProtocolFactory.create(this.context.getResources(), vvmType); } public PersistableBundle getConfig() { PersistableBundle result = new PersistableBundle(); if (telephonyConfig != null) { result.putAll(telephonyConfig); } if (carrierConfig != null) { result.putAll(carrierConfig); } return result; } public Context getContext() { return context; } @Nullable public PhoneAccountHandle getPhoneAccountHandle() { return phoneAccountHandle; } /** * return whether the carrier's visual voicemail is supported, with KEY_VVM_TYPE_STRING set as a * known protocol. */ public boolean isValid() { if (protocol == null) { return false; } return true; } @Nullable public String getVvmType() { return (String) getValue(KEY_VVM_TYPE_STRING); } @Nullable public VisualVoicemailProtocol getProtocol() { return protocol; } /** @returns arbitrary String stored in the config file. Used for protocol specific values. */ @Nullable public String getString(String key) { Assert.checkArgument(isValid()); return (String) getValue(key); } @Nullable private Set getCarrierVvmPackageNamesWithoutValidation() { Set names = getCarrierVvmPackageNames(overrideConfig); if (names != null) { return names; } names = getCarrierVvmPackageNames(carrierConfig); if (names != null) { return names; } return getCarrierVvmPackageNames(telephonyConfig); } @Nullable public Set getCarrierVvmPackageNames() { Assert.checkArgument(isValid()); return getCarrierVvmPackageNamesWithoutValidation(); } private static Set getCarrierVvmPackageNames(@Nullable PersistableBundle bundle) { if (bundle == null) { return null; } Set names = new ArraySet<>(); if (bundle.containsKey(KEY_CARRIER_VVM_PACKAGE_NAME_STRING)) { names.add(bundle.getString(KEY_CARRIER_VVM_PACKAGE_NAME_STRING)); } if (bundle.containsKey(KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY)) { String[] vvmPackages = bundle.getStringArray(KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY); if (vvmPackages != null && vvmPackages.length > 0) { Collections.addAll(names, vvmPackages); } } if (names.isEmpty()) { return null; } return names; } /** * For checking upon sim insertion whether visual voicemail should be enabled. This method does so * by checking if the carrier's voicemail app is installed. */ public boolean isEnabledByDefault() { if (!isValid()) { return false; } return !isCarrierAppInstalled(); } public boolean isCellularDataRequired() { Assert.checkArgument(isValid()); return (boolean) getValue(KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL, false); } public boolean isPrefetchEnabled() { Assert.checkArgument(isValid()); return (boolean) getValue(KEY_VVM_PREFETCH_BOOL, true); } public int getApplicationPort() { Assert.checkArgument(isValid()); return (int) getValue(KEY_VVM_PORT_NUMBER_INT, 0); } @Nullable public String getDestinationNumber() { Assert.checkArgument(isValid()); return (String) getValue(KEY_VVM_DESTINATION_NUMBER_STRING); } /** @return Port to start a SSL IMAP connection directly. */ public int getSslPort() { Assert.checkArgument(isValid()); return (int) getValue(KEY_VVM_SSL_PORT_NUMBER_INT, 0); } /** * Hidden Config. * *

Sometimes the server states it supports a certain feature but we found they have bug on the * server side. For example, in a bug the server reported AUTH=DIGEST-MD5 capability but * using it to login will cause subsequent response to be erroneous. * * @return A set of capabilities that is reported by the IMAP CAPABILITY command, but determined * to have issues and should not be used. */ @Nullable public Set getDisabledCapabilities() { Assert.checkArgument(isValid()); Set disabledCapabilities; disabledCapabilities = getDisabledCapabilities(overrideConfig); if (disabledCapabilities != null) { return disabledCapabilities; } disabledCapabilities = getDisabledCapabilities(carrierConfig); if (disabledCapabilities != null) { return disabledCapabilities; } return getDisabledCapabilities(telephonyConfig); } @Nullable private static Set getDisabledCapabilities(@Nullable PersistableBundle bundle) { if (bundle == null) { return null; } if (!bundle.containsKey(KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY)) { return null; } String[] disabledCapabilities = bundle.getStringArray(KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY); if (disabledCapabilities != null && disabledCapabilities.length > 0) { ArraySet result = new ArraySet<>(); Collections.addAll(result, disabledCapabilities); return result; } return null; } public String getClientPrefix() { Assert.checkArgument(isValid()); String prefix = (String) getValue(KEY_VVM_CLIENT_PREFIX_STRING); if (prefix != null) { return prefix; } return "//VVM"; } /** * Should legacy mode be used when the OMTP VVM client is disabled? * *

Legacy mode is a mode that on the carrier side visual voicemail is still activated, but on * the client side all network operations are disabled. SMSs are still monitored so a new message * SYNC SMS will be translated to show a message waiting indicator, like traditional voicemails. * *

This is for carriers that does not support VVM deactivation so voicemail can continue to * function without the data cost. */ public boolean isLegacyModeEnabled() { Assert.checkArgument(isValid()); return (boolean) getValue(KEY_VVM_LEGACY_MODE_ENABLED_BOOL, false); } public void startActivation() { PhoneAccountHandle phoneAccountHandle = getPhoneAccountHandle(); if (phoneAccountHandle == null) { // This should never happen // Error logged in getPhoneAccountHandle(). return; } if (!isValid()) { VvmLog.e(TAG, "startActivation : invalid config for account " + phoneAccountHandle); return; } ActivationTask.start(context, this.phoneAccountHandle, null); } public void activateSmsFilter() { Assert.checkArgument(isValid()); context .getSystemService(TelephonyManager.class) .createForPhoneAccountHandle(getPhoneAccountHandle()) .setVisualVoicemailSmsFilterSettings( new VisualVoicemailSmsFilterSettings.Builder() .setClientPrefix(getClientPrefix()) .build()); } public void startDeactivation() { VvmLog.i(TAG, "startDeactivation"); if (isValid()) { if (!isLegacyModeEnabled()) { // SMS should still be filtered in legacy mode context .getSystemService(TelephonyManager.class) .createForPhoneAccountHandle(getPhoneAccountHandle()) .setVisualVoicemailSmsFilterSettings(null); VvmLog.i(TAG, "filter disabled"); } protocol.startDeactivation(this); } VvmAccountManager.removeAccount(context, getPhoneAccountHandle()); } public boolean supportsProvisioning() { Assert.checkArgument(isValid()); return protocol.supportsProvisioning(); } public void startProvisioning( ActivationTask task, PhoneAccountHandle phone, VoicemailStatus.Editor status, StatusMessage message, Bundle data, boolean isCarrierInitiated) { Assert.checkArgument(isValid()); protocol.startProvisioning(task, phone, this, status, message, data, isCarrierInitiated); } public void requestStatus(@Nullable PendingIntent sentIntent) { Assert.checkArgument(isValid()); protocol.requestStatus(this, sentIntent); } public void handleEvent(VoicemailStatus.Editor status, OmtpEvents event) { Assert.checkArgument(isValid()); VvmLog.i(TAG, "OmtpEvent:" + event); protocol.handleEvent(context, this, status, event); } @Override public String toString() { StringBuilder builder = new StringBuilder("OmtpVvmCarrierConfigHelper ["); builder .append("phoneAccountHandle: ") .append(phoneAccountHandle) .append(", carrierConfig: ") .append(carrierConfig != null) .append(", telephonyConfig: ") .append(telephonyConfig != null) .append(", type: ") .append(getVvmType()) .append(", destinationNumber: ") .append(getDestinationNumber()) .append(", applicationPort: ") .append(getApplicationPort()) .append(", sslPort: ") .append(getSslPort()) .append(", isEnabledByDefault: ") .append(isEnabledByDefault()) .append(", isCellularDataRequired: ") .append(isCellularDataRequired()) .append(", isPrefetchEnabled: ") .append(isPrefetchEnabled()) .append(", isLegacyModeEnabled: ") .append(isLegacyModeEnabled()) .append("]"); return builder.toString(); } @Nullable private PersistableBundle getCarrierConfig(@NonNull TelephonyManager telephonyManager) { CarrierConfigManager carrierConfigManager = (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE); if (carrierConfigManager == null) { VvmLog.w(TAG, "No carrier config service found."); return null; } PersistableBundle config = telephonyManager.getCarrierConfig(); if (config == null) { return null; } if (TextUtils.isEmpty(config.getString(CarrierConfigManager.KEY_VVM_TYPE_STRING))) { return null; } return config; } @Nullable private Object getValue(String key) { return getValue(key, null); } @Nullable private Object getValue(String key, Object defaultValue) { Object result; if (overrideConfig != null) { result = overrideConfig.get(key); if (result != null) { return result; } } if (carrierConfig != null) { result = carrierConfig.get(key); if (result != null) { return result; } } if (telephonyConfig != null) { result = telephonyConfig.get(key); if (result != null) { return result; } } return defaultValue; } @VisibleForTesting public static void setOverrideConfigForTest(PersistableBundle config) { overrideConfigForTest = config; } /** Checks if the carrier VVM app is installed. */ public boolean isCarrierAppInstalled() { Set carrierPackages = getCarrierVvmPackageNamesWithoutValidation(); if (carrierPackages == null) { return false; } for (String packageName : carrierPackages) { try { ApplicationInfo info = getContext().getPackageManager().getApplicationInfo(packageName, 0); if (!info.enabled) { continue; } return true; } catch (NameNotFoundException e) { continue; } } return false; } /** * Suppress the behavior of treating any text attachment with MIME "text/*" as transcription, * default to false. */ public boolean ignoreTranscription() { Assert.checkArgument(isValid()); return (boolean) getValue(KEY_IGNORE_TRANSCRIPTION_BOOL, false); } }