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