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