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