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