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