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