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 OmtpVvmCarrierConfigHelper(Context context, @Nullable PhoneAccountHandle handle)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 OmtpVvmCarrierConfigHelper( Context context, PersistableBundle carrierConfig, PersistableBundle telephonyConfig)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 getConfig()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 getContext()166 public Context getContext() { 167 return context; 168 } 169 170 @Nullable getPhoneAccountHandle()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 */ isValid()179 public boolean isValid() { 180 if (protocol == null) { 181 return false; 182 } 183 return true; 184 } 185 186 @Nullable getVvmType()187 public String getVvmType() { 188 return (String) getValue(KEY_VVM_TYPE_STRING); 189 } 190 191 @Nullable getProtocol()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 getString(String key)198 public String getString(String key) { 199 Assert.checkArgument(isValid()); 200 return (String) getValue(key); 201 } 202 203 @Nullable getCarrierVvmPackageNamesWithoutValidation()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 getCarrierVvmPackageNames()217 public Set<String> getCarrierVvmPackageNames() { 218 Assert.checkArgument(isValid()); 219 return getCarrierVvmPackageNamesWithoutValidation(); 220 } 221 getCarrierVvmPackageNames(@ullable PersistableBundle bundle)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 */ isEnabledByDefault()246 public boolean isEnabledByDefault() { 247 if (!isValid()) { 248 return false; 249 } 250 return !isCarrierAppInstalled(); 251 } 252 isCellularDataRequired()253 public boolean isCellularDataRequired() { 254 Assert.checkArgument(isValid()); 255 return (boolean) getValue(KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL, false); 256 } 257 isPrefetchEnabled()258 public boolean isPrefetchEnabled() { 259 Assert.checkArgument(isValid()); 260 return (boolean) getValue(KEY_VVM_PREFETCH_BOOL, true); 261 } 262 getApplicationPort()263 public int getApplicationPort() { 264 Assert.checkArgument(isValid()); 265 return (int) getValue(KEY_VVM_PORT_NUMBER_INT, 0); 266 } 267 268 @Nullable getDestinationNumber()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. */ getSslPort()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 getDisabledCapabilities()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 getDisabledCapabilities(@ullable PersistableBundle bundle)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 getClientPrefix()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 */ isLegacyModeEnabled()342 public boolean isLegacyModeEnabled() { 343 Assert.checkArgument(isValid()); 344 return (boolean) getValue(KEY_VVM_LEGACY_MODE_ENABLED_BOOL, false); 345 } 346 startActivation()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 activateSmsFilter()369 public void activateSmsFilter() { 370 Assert.checkArgument(isValid()); 371 TelephonyMangerCompat.setVisualVoicemailSmsFilterSettings( 372 context, 373 getPhoneAccountHandle(), 374 new VisualVoicemailSmsFilterSettings.Builder().setClientPrefix(getClientPrefix()).build()); 375 } 376 startDeactivation()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 supportsProvisioning()392 public boolean supportsProvisioning() { 393 Assert.checkArgument(isValid()); 394 return protocol.supportsProvisioning(); 395 } 396 startProvisioning( ActivationTask task, PhoneAccountHandle phone, VoicemailStatus.Editor status, StatusMessage message, Bundle data, boolean isCarrierInitiated)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 requestStatus(@ullable PendingIntent sentIntent)408 public void requestStatus(@Nullable PendingIntent sentIntent) { 409 Assert.checkArgument(isValid()); 410 protocol.requestStatus(this, sentIntent); 411 } 412 handleEvent(VoicemailStatus.Editor status, OmtpEvents event)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 toString()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 getCarrierConfig(@onNull TelephonyManager telephonyManager)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 getValue(String key)467 private Object getValue(String key) { 468 return getValue(key, null); 469 } 470 471 @Nullable getValue(String key, Object defaultValue)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 setOverrideConfigForTest(PersistableBundle config)497 public static void setOverrideConfigForTest(PersistableBundle config) { 498 overrideConfigForTest = config; 499 } 500 501 /** Checks if the carrier VVM app is installed. */ isCarrierAppInstalled()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