• 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.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