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}