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