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