Home | History | Annotate | Download | only in impl
      1 /*
      2  * Copyright (C) 2015 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 package com.android.voicemail.impl;
     17 
     18 import android.annotation.TargetApi;
     19 import android.app.PendingIntent;
     20 import android.content.Context;
     21 import android.content.pm.ApplicationInfo;
     22 import android.content.pm.PackageManager.NameNotFoundException;
     23 import android.os.Build.VERSION_CODES;
     24 import android.os.Bundle;
     25 import android.os.PersistableBundle;
     26 import android.support.annotation.NonNull;
     27 import android.support.annotation.Nullable;
     28 import android.support.annotation.VisibleForTesting;
     29 import android.telecom.PhoneAccountHandle;
     30 import android.telephony.CarrierConfigManager;
     31 import android.telephony.TelephonyManager;
     32 import android.telephony.VisualVoicemailSmsFilterSettings;
     33 import android.text.TextUtils;
     34 import android.util.ArraySet;
     35 import com.android.dialer.common.Assert;
     36 import com.android.voicemail.impl.configui.ConfigOverrideFragment;
     37 import com.android.voicemail.impl.protocol.VisualVoicemailProtocol;
     38 import com.android.voicemail.impl.protocol.VisualVoicemailProtocolFactory;
     39 import com.android.voicemail.impl.sms.StatusMessage;
     40 import com.android.voicemail.impl.sync.VvmAccountManager;
     41 import java.util.Collections;
     42 import java.util.Set;
     43 
     44 /**
     45  * Manages carrier dependent visual voicemail configuration values. The primary source is the value
     46  * retrieved from CarrierConfigManager. If CarrierConfigManager does not provide the config
     47  * (KEY_VVM_TYPE_STRING is empty, or "hidden" configs), then the value hardcoded in telephony will
     48  * be used (in res/xml/vvm_config.xml)
     49  *
     50  * <p>Hidden configs are new configs that are planned for future APIs, or miscellaneous settings
     51  * that may clutter CarrierConfigManager too much.
     52  *
     53  * <p>The current hidden configs are: {@link #getSslPort()} {@link #getDisabledCapabilities()}
     54  *
     55  * <p>TODO(twyen): refactor this to an interface.
     56  */
     57 @TargetApi(VERSION_CODES.O)
     58 @SuppressWarnings("missingpermission")
     59 public class OmtpVvmCarrierConfigHelper {
     60 
     61   private static final String TAG = "OmtpVvmCarrierCfgHlpr";
     62 
     63   public static final String KEY_VVM_TYPE_STRING = CarrierConfigManager.KEY_VVM_TYPE_STRING;
     64   public static final String KEY_VVM_DESTINATION_NUMBER_STRING =
     65       CarrierConfigManager.KEY_VVM_DESTINATION_NUMBER_STRING;
     66   public static final String KEY_VVM_PORT_NUMBER_INT = CarrierConfigManager.KEY_VVM_PORT_NUMBER_INT;
     67   public static final String KEY_CARRIER_VVM_PACKAGE_NAME_STRING =
     68       CarrierConfigManager.KEY_CARRIER_VVM_PACKAGE_NAME_STRING;
     69   public static final String KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY =
     70       "carrier_vvm_package_name_string_array";
     71   public static final String KEY_VVM_PREFETCH_BOOL = CarrierConfigManager.KEY_VVM_PREFETCH_BOOL;
     72   public static final String KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL =
     73       CarrierConfigManager.KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL;
     74 
     75   /** @see #getSslPort() */
     76   public static final String KEY_VVM_SSL_PORT_NUMBER_INT = "vvm_ssl_port_number_int";
     77 
     78   /** @see #isLegacyModeEnabled() */
     79   public static final String KEY_VVM_LEGACY_MODE_ENABLED_BOOL = "vvm_legacy_mode_enabled_bool";
     80 
     81   /**
     82    * Ban a capability reported by the server from being used. The array of string should be a subset
     83    * of the capabilities returned IMAP CAPABILITY command.
     84    *
     85    * @see #getDisabledCapabilities()
     86    */
     87   public static final String KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY =
     88       "vvm_disabled_capabilities_string_array";
     89 
     90   public static final String KEY_VVM_CLIENT_PREFIX_STRING = "vvm_client_prefix_string";
     91 
     92   @Nullable private static PersistableBundle overrideConfigForTest;
     93 
     94   private final Context context;
     95   private final PersistableBundle carrierConfig;
     96   private final String vvmType;
     97   private final VisualVoicemailProtocol protocol;
     98   private final PersistableBundle telephonyConfig;
     99 
    100   @Nullable private final PersistableBundle overrideConfig;
    101 
    102   private PhoneAccountHandle phoneAccountHandle;
    103 
    104   public OmtpVvmCarrierConfigHelper(Context context, @Nullable PhoneAccountHandle handle) {
    105     this.context = context;
    106     phoneAccountHandle = handle;
    107     TelephonyManager telephonyManager =
    108         context
    109             .getSystemService(TelephonyManager.class)
    110             .createForPhoneAccountHandle(phoneAccountHandle);
    111     if (telephonyManager == null) {
    112       VvmLog.e(TAG, "PhoneAccountHandle is invalid");
    113       carrierConfig = null;
    114       telephonyConfig = null;
    115       overrideConfig = null;
    116       vvmType = null;
    117       protocol = null;
    118       return;
    119     }
    120 
    121     if (overrideConfigForTest != null) {
    122       overrideConfig = overrideConfigForTest;
    123       carrierConfig = new PersistableBundle();
    124       telephonyConfig = new PersistableBundle();
    125     } else {
    126       if (ConfigOverrideFragment.isOverridden(context)) {
    127         overrideConfig = ConfigOverrideFragment.getConfig(context);
    128         VvmLog.w(TAG, "Config override is activated: " + overrideConfig);
    129       } else {
    130         overrideConfig = null;
    131       }
    132 
    133       carrierConfig = getCarrierConfig(telephonyManager);
    134       telephonyConfig =
    135           new DialerVvmConfigManager(context)
    136               .getConfig(CarrierIdentifier.forHandle(context, phoneAccountHandle));
    137     }
    138 
    139     vvmType = getVvmType();
    140     protocol = VisualVoicemailProtocolFactory.create(this.context.getResources(), vvmType);
    141   }
    142 
    143   @VisibleForTesting
    144   OmtpVvmCarrierConfigHelper(
    145       Context context, PersistableBundle carrierConfig, PersistableBundle telephonyConfig) {
    146     this.context = context;
    147     this.carrierConfig = carrierConfig;
    148     this.telephonyConfig = telephonyConfig;
    149     overrideConfig = null;
    150     vvmType = getVvmType();
    151     protocol = VisualVoicemailProtocolFactory.create(this.context.getResources(), vvmType);
    152   }
    153 
    154   public PersistableBundle getConfig() {
    155     PersistableBundle result = new PersistableBundle();
    156     if (telephonyConfig != null) {
    157       result.putAll(telephonyConfig);
    158     }
    159     if (carrierConfig != null) {
    160       result.putAll(carrierConfig);
    161     }
    162 
    163     return result;
    164   }
    165 
    166   public Context getContext() {
    167     return context;
    168   }
    169 
    170   @Nullable
    171   public PhoneAccountHandle getPhoneAccountHandle() {
    172     return phoneAccountHandle;
    173   }
    174 
    175   /**
    176    * return whether the carrier's visual voicemail is supported, with KEY_VVM_TYPE_STRING set as a
    177    * known protocol.
    178    */
    179   public boolean isValid() {
    180     if (protocol == null) {
    181       return false;
    182     }
    183     return true;
    184   }
    185 
    186   @Nullable
    187   public String getVvmType() {
    188     return (String) getValue(KEY_VVM_TYPE_STRING);
    189   }
    190 
    191   @Nullable
    192   public VisualVoicemailProtocol getProtocol() {
    193     return protocol;
    194   }
    195 
    196   /** @returns arbitrary String stored in the config file. Used for protocol specific values. */
    197   @Nullable
    198   public String getString(String key) {
    199     Assert.checkArgument(isValid());
    200     return (String) getValue(key);
    201   }
    202 
    203   @Nullable
    204   private Set<String> getCarrierVvmPackageNamesWithoutValidation() {
    205     Set<String> names = getCarrierVvmPackageNames(overrideConfig);
    206     if (names != null) {
    207       return names;
    208     }
    209     names = getCarrierVvmPackageNames(carrierConfig);
    210     if (names != null) {
    211       return names;
    212     }
    213     return getCarrierVvmPackageNames(telephonyConfig);
    214   }
    215 
    216   @Nullable
    217   public Set<String> getCarrierVvmPackageNames() {
    218     Assert.checkArgument(isValid());
    219     return getCarrierVvmPackageNamesWithoutValidation();
    220   }
    221 
    222   private static Set<String> getCarrierVvmPackageNames(@Nullable PersistableBundle bundle) {
    223     if (bundle == null) {
    224       return null;
    225     }
    226     Set<String> names = new ArraySet<>();
    227     if (bundle.containsKey(KEY_CARRIER_VVM_PACKAGE_NAME_STRING)) {
    228       names.add(bundle.getString(KEY_CARRIER_VVM_PACKAGE_NAME_STRING));
    229     }
    230     if (bundle.containsKey(KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY)) {
    231       String[] vvmPackages = bundle.getStringArray(KEY_CARRIER_VVM_PACKAGE_NAME_STRING_ARRAY);
    232       if (vvmPackages != null && vvmPackages.length > 0) {
    233         Collections.addAll(names, vvmPackages);
    234       }
    235     }
    236     if (names.isEmpty()) {
    237       return null;
    238     }
    239     return names;
    240   }
    241 
    242   /**
    243    * For checking upon sim insertion whether visual voicemail should be enabled. This method does so
    244    * by checking if the carrier's voicemail app is installed.
    245    */
    246   public boolean isEnabledByDefault() {
    247     if (!isValid()) {
    248       return false;
    249     }
    250     return !isCarrierAppInstalled();
    251   }
    252 
    253   public boolean isCellularDataRequired() {
    254     Assert.checkArgument(isValid());
    255     return (boolean) getValue(KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL, false);
    256   }
    257 
    258   public boolean isPrefetchEnabled() {
    259     Assert.checkArgument(isValid());
    260     return (boolean) getValue(KEY_VVM_PREFETCH_BOOL, true);
    261   }
    262 
    263   public int getApplicationPort() {
    264     Assert.checkArgument(isValid());
    265     return (int) getValue(KEY_VVM_PORT_NUMBER_INT, 0);
    266   }
    267 
    268   @Nullable
    269   public String getDestinationNumber() {
    270     Assert.checkArgument(isValid());
    271     return (String) getValue(KEY_VVM_DESTINATION_NUMBER_STRING);
    272   }
    273 
    274   /** @return Port to start a SSL IMAP connection directly. */
    275   public int getSslPort() {
    276     Assert.checkArgument(isValid());
    277     return (int) getValue(KEY_VVM_SSL_PORT_NUMBER_INT, 0);
    278   }
    279 
    280   /**
    281    * Hidden Config.
    282    *
    283    * <p>Sometimes the server states it supports a certain feature but we found they have bug on the
    284    * server side. For example, in a bug the server reported AUTH=DIGEST-MD5 capability but
    285    * using it to login will cause subsequent response to be erroneous.
    286    *
    287    * @return A set of capabilities that is reported by the IMAP CAPABILITY command, but determined
    288    *     to have issues and should not be used.
    289    */
    290   @Nullable
    291   public Set<String> getDisabledCapabilities() {
    292     Assert.checkArgument(isValid());
    293     Set<String> disabledCapabilities;
    294     disabledCapabilities = getDisabledCapabilities(overrideConfig);
    295     if (disabledCapabilities != null) {
    296       return disabledCapabilities;
    297     }
    298     disabledCapabilities = getDisabledCapabilities(carrierConfig);
    299     if (disabledCapabilities != null) {
    300       return disabledCapabilities;
    301     }
    302     return getDisabledCapabilities(telephonyConfig);
    303   }
    304 
    305   @Nullable
    306   private static Set<String> getDisabledCapabilities(@Nullable PersistableBundle bundle) {
    307     if (bundle == null) {
    308       return null;
    309     }
    310     if (!bundle.containsKey(KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY)) {
    311       return null;
    312     }
    313     String[] disabledCapabilities =
    314         bundle.getStringArray(KEY_VVM_DISABLED_CAPABILITIES_STRING_ARRAY);
    315     if (disabledCapabilities != null && disabledCapabilities.length > 0) {
    316       ArraySet<String> result = new ArraySet<>();
    317       Collections.addAll(result, disabledCapabilities);
    318       return result;
    319     }
    320     return null;
    321   }
    322 
    323   public String getClientPrefix() {
    324     Assert.checkArgument(isValid());
    325     String prefix = (String) getValue(KEY_VVM_CLIENT_PREFIX_STRING);
    326     if (prefix != null) {
    327       return prefix;
    328     }
    329     return "//VVM";
    330   }
    331 
    332   /**
    333    * Should legacy mode be used when the OMTP VVM client is disabled?
    334    *
    335    * <p>Legacy mode is a mode that on the carrier side visual voicemail is still activated, but on
    336    * the client side all network operations are disabled. SMSs are still monitored so a new message
    337    * SYNC SMS will be translated to show a message waiting indicator, like traditional voicemails.
    338    *
    339    * <p>This is for carriers that does not support VVM deactivation so voicemail can continue to
    340    * function without the data cost.
    341    */
    342   public boolean isLegacyModeEnabled() {
    343     Assert.checkArgument(isValid());
    344     return (boolean) getValue(KEY_VVM_LEGACY_MODE_ENABLED_BOOL, false);
    345   }
    346 
    347   public void startActivation() {
    348     Assert.checkArgument(isValid());
    349     PhoneAccountHandle phoneAccountHandle = getPhoneAccountHandle();
    350     if (phoneAccountHandle == null) {
    351       // This should never happen
    352       // Error logged in getPhoneAccountHandle().
    353       return;
    354     }
    355 
    356     if (vvmType == null || vvmType.isEmpty()) {
    357       // The VVM type is invalid; we should never have gotten here in the first place since
    358       // this is loaded initially in the constructor, and callers should check isValid()
    359       // before trying to start activation anyways.
    360       VvmLog.e(TAG, "startActivation : vvmType is null or empty for account " + phoneAccountHandle);
    361       return;
    362     }
    363 
    364     if (protocol != null) {
    365       ActivationTask.start(context, this.phoneAccountHandle, null);
    366     }
    367   }
    368 
    369   public void activateSmsFilter() {
    370     Assert.checkArgument(isValid());
    371     TelephonyMangerCompat.setVisualVoicemailSmsFilterSettings(
    372         context,
    373         getPhoneAccountHandle(),
    374         new VisualVoicemailSmsFilterSettings.Builder().setClientPrefix(getClientPrefix()).build());
    375   }
    376 
    377   public void startDeactivation() {
    378     Assert.checkArgument(isValid());
    379     VvmLog.i(TAG, "startDeactivation");
    380     if (!isLegacyModeEnabled()) {
    381       // SMS should still be filtered in legacy mode
    382       TelephonyMangerCompat.setVisualVoicemailSmsFilterSettings(
    383           context, getPhoneAccountHandle(), null);
    384       VvmLog.i(TAG, "filter disabled");
    385     }
    386     if (protocol != null) {
    387       protocol.startDeactivation(this);
    388     }
    389     VvmAccountManager.removeAccount(context, getPhoneAccountHandle());
    390   }
    391 
    392   public boolean supportsProvisioning() {
    393     Assert.checkArgument(isValid());
    394     return protocol.supportsProvisioning();
    395   }
    396 
    397   public void startProvisioning(
    398       ActivationTask task,
    399       PhoneAccountHandle phone,
    400       VoicemailStatus.Editor status,
    401       StatusMessage message,
    402       Bundle data,
    403       boolean isCarrierInitiated) {
    404     Assert.checkArgument(isValid());
    405     protocol.startProvisioning(task, phone, this, status, message, data, isCarrierInitiated);
    406   }
    407 
    408   public void requestStatus(@Nullable PendingIntent sentIntent) {
    409     Assert.checkArgument(isValid());
    410     protocol.requestStatus(this, sentIntent);
    411   }
    412 
    413   public void handleEvent(VoicemailStatus.Editor status, OmtpEvents event) {
    414     Assert.checkArgument(isValid());
    415     VvmLog.i(TAG, "OmtpEvent:" + event);
    416     protocol.handleEvent(context, this, status, event);
    417   }
    418 
    419   @Override
    420   public String toString() {
    421     StringBuilder builder = new StringBuilder("OmtpVvmCarrierConfigHelper [");
    422     builder
    423         .append("phoneAccountHandle: ")
    424         .append(phoneAccountHandle)
    425         .append(", carrierConfig: ")
    426         .append(carrierConfig != null)
    427         .append(", telephonyConfig: ")
    428         .append(telephonyConfig != null)
    429         .append(", type: ")
    430         .append(getVvmType())
    431         .append(", destinationNumber: ")
    432         .append(getDestinationNumber())
    433         .append(", applicationPort: ")
    434         .append(getApplicationPort())
    435         .append(", sslPort: ")
    436         .append(getSslPort())
    437         .append(", isEnabledByDefault: ")
    438         .append(isEnabledByDefault())
    439         .append(", isCellularDataRequired: ")
    440         .append(isCellularDataRequired())
    441         .append(", isPrefetchEnabled: ")
    442         .append(isPrefetchEnabled())
    443         .append(", isLegacyModeEnabled: ")
    444         .append(isLegacyModeEnabled())
    445         .append("]");
    446     return builder.toString();
    447   }
    448 
    449   @Nullable
    450   private PersistableBundle getCarrierConfig(@NonNull TelephonyManager telephonyManager) {
    451     CarrierConfigManager carrierConfigManager =
    452         (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
    453     if (carrierConfigManager == null) {
    454       VvmLog.w(TAG, "No carrier config service found.");
    455       return null;
    456     }
    457 
    458     PersistableBundle config = telephonyManager.getCarrierConfig();
    459 
    460     if (TextUtils.isEmpty(config.getString(CarrierConfigManager.KEY_VVM_TYPE_STRING))) {
    461       return null;
    462     }
    463     return config;
    464   }
    465 
    466   @Nullable
    467   private Object getValue(String key) {
    468     return getValue(key, null);
    469   }
    470 
    471   @Nullable
    472   private Object getValue(String key, Object defaultValue) {
    473     Object result;
    474     if (overrideConfig != null) {
    475       result = overrideConfig.get(key);
    476       if (result != null) {
    477         return result;
    478       }
    479     }
    480 
    481     if (carrierConfig != null) {
    482       result = carrierConfig.get(key);
    483       if (result != null) {
    484         return result;
    485       }
    486     }
    487     if (telephonyConfig != null) {
    488       result = telephonyConfig.get(key);
    489       if (result != null) {
    490         return result;
    491       }
    492     }
    493     return defaultValue;
    494   }
    495 
    496   @VisibleForTesting
    497   public static void setOverrideConfigForTest(PersistableBundle config) {
    498     overrideConfigForTest = config;
    499   }
    500 
    501   /** Checks if the carrier VVM app is installed. */
    502   public boolean isCarrierAppInstalled() {
    503     Set<String> carrierPackages = getCarrierVvmPackageNamesWithoutValidation();
    504     if (carrierPackages == null) {
    505       return false;
    506     }
    507     for (String packageName : carrierPackages) {
    508       try {
    509         ApplicationInfo info = getContext().getPackageManager().getApplicationInfo(packageName, 0);
    510         if (!info.enabled) {
    511           continue;
    512         }
    513         return true;
    514       } catch (NameNotFoundException e) {
    515         continue;
    516       }
    517     }
    518     return false;
    519   }
    520 }
    521