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