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