• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
OmtpVvmCarrierConfigHelper(Context context, @Nullable PhoneAccountHandle handle)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
OmtpVvmCarrierConfigHelper( Context context, PersistableBundle carrierConfig, PersistableBundle telephonyConfig)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 
getConfig()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 
getContext()166   public Context getContext() {
167     return context;
168   }
169 
170   @Nullable
getPhoneAccountHandle()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    */
isValid()179   public boolean isValid() {
180     if (protocol == null) {
181       return false;
182     }
183     return true;
184   }
185 
186   @Nullable
getVvmType()187   public String getVvmType() {
188     return (String) getValue(KEY_VVM_TYPE_STRING);
189   }
190 
191   @Nullable
getProtocol()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
getString(String key)198   public String getString(String key) {
199     Assert.checkArgument(isValid());
200     return (String) getValue(key);
201   }
202 
203   @Nullable
getCarrierVvmPackageNamesWithoutValidation()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
getCarrierVvmPackageNames()217   public Set<String> getCarrierVvmPackageNames() {
218     Assert.checkArgument(isValid());
219     return getCarrierVvmPackageNamesWithoutValidation();
220   }
221 
getCarrierVvmPackageNames(@ullable PersistableBundle bundle)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    */
isEnabledByDefault()246   public boolean isEnabledByDefault() {
247     if (!isValid()) {
248       return false;
249     }
250     return !isCarrierAppInstalled();
251   }
252 
isCellularDataRequired()253   public boolean isCellularDataRequired() {
254     Assert.checkArgument(isValid());
255     return (boolean) getValue(KEY_VVM_CELLULAR_DATA_REQUIRED_BOOL, false);
256   }
257 
isPrefetchEnabled()258   public boolean isPrefetchEnabled() {
259     Assert.checkArgument(isValid());
260     return (boolean) getValue(KEY_VVM_PREFETCH_BOOL, true);
261   }
262 
getApplicationPort()263   public int getApplicationPort() {
264     Assert.checkArgument(isValid());
265     return (int) getValue(KEY_VVM_PORT_NUMBER_INT, 0);
266   }
267 
268   @Nullable
getDestinationNumber()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. */
getSslPort()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
getDisabledCapabilities()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
getDisabledCapabilities(@ullable PersistableBundle bundle)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 
getClientPrefix()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    */
isLegacyModeEnabled()342   public boolean isLegacyModeEnabled() {
343     Assert.checkArgument(isValid());
344     return (boolean) getValue(KEY_VVM_LEGACY_MODE_ENABLED_BOOL, false);
345   }
346 
startActivation()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 
activateSmsFilter()369   public void activateSmsFilter() {
370     Assert.checkArgument(isValid());
371     TelephonyMangerCompat.setVisualVoicemailSmsFilterSettings(
372         context,
373         getPhoneAccountHandle(),
374         new VisualVoicemailSmsFilterSettings.Builder().setClientPrefix(getClientPrefix()).build());
375   }
376 
startDeactivation()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 
supportsProvisioning()392   public boolean supportsProvisioning() {
393     Assert.checkArgument(isValid());
394     return protocol.supportsProvisioning();
395   }
396 
startProvisioning( ActivationTask task, PhoneAccountHandle phone, VoicemailStatus.Editor status, StatusMessage message, Bundle data, boolean isCarrierInitiated)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 
requestStatus(@ullable PendingIntent sentIntent)408   public void requestStatus(@Nullable PendingIntent sentIntent) {
409     Assert.checkArgument(isValid());
410     protocol.requestStatus(this, sentIntent);
411   }
412 
handleEvent(VoicemailStatus.Editor status, OmtpEvents event)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
toString()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
getCarrierConfig(@onNull TelephonyManager telephonyManager)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
getValue(String key)467   private Object getValue(String key) {
468     return getValue(key, null);
469   }
470 
471   @Nullable
getValue(String key, Object defaultValue)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
setOverrideConfigForTest(PersistableBundle config)497   public static void setOverrideConfigForTest(PersistableBundle config) {
498     overrideConfigForTest = config;
499   }
500 
501   /** Checks if the carrier VVM app is installed. */
isCarrierAppInstalled()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