• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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 
17 package com.android.phone.vvm.omtp.protocol;
18 
19 import android.annotation.Nullable;
20 import android.app.PendingIntent;
21 import android.content.Context;
22 import android.net.Network;
23 import android.os.Bundle;
24 import android.telecom.PhoneAccountHandle;
25 import android.telephony.SmsManager;
26 import android.text.TextUtils;
27 
28 import com.android.phone.PhoneGlobals;
29 import com.android.phone.VoicemailStatus;
30 import com.android.phone.common.mail.MessagingException;
31 import com.android.phone.settings.VisualVoicemailSettingsUtil;
32 import com.android.phone.settings.VoicemailChangePinActivity;
33 import com.android.phone.vvm.omtp.ActivationTask;
34 import com.android.phone.vvm.omtp.OmtpConstants;
35 import com.android.phone.vvm.omtp.OmtpEvents;
36 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
37 import com.android.phone.vvm.omtp.VisualVoicemailPreferences;
38 import com.android.phone.vvm.omtp.VvmLog;
39 import com.android.phone.vvm.omtp.imap.ImapHelper;
40 import com.android.phone.vvm.omtp.imap.ImapHelper.InitializingException;
41 import com.android.phone.vvm.omtp.sms.OmtpMessageSender;
42 import com.android.phone.vvm.omtp.sms.StatusMessage;
43 import com.android.phone.vvm.omtp.sms.Vvm3MessageSender;
44 import com.android.phone.vvm.omtp.sync.VvmNetworkRequest;
45 import com.android.phone.vvm.omtp.sync.VvmNetworkRequest.NetworkWrapper;
46 import com.android.phone.vvm.omtp.sync.VvmNetworkRequest.RequestFailedException;
47 
48 import java.io.IOException;
49 import java.security.SecureRandom;
50 import java.util.Locale;
51 
52 /**
53  * A flavor of OMTP protocol with a different provisioning process
54  *
55  * Used by carriers such as Verizon Wireless
56  */
57 public class Vvm3Protocol extends VisualVoicemailProtocol {
58 
59     private static final String TAG = "Vvm3Protocol";
60 
61     private static final String SMS_EVENT_UNRECOGNIZED = "UNRECOGNIZED";
62     private static final String SMS_EVENT_UNRECOGNIZED_CMD = "cmd";
63     private static final String SMS_EVENT_UNRECOGNIZED_STATUS = "STATUS";
64     private static final String DEFAULT_VMG_URL_KEY = "default_vmg_url";
65 
66     private static final String IMAP_CHANGE_TUI_PWD_FORMAT = "CHANGE_TUI_PWD PWD=%1$s OLD_PWD=%2$s";
67     private static final String IMAP_CHANGE_VM_LANG_FORMAT = "CHANGE_VM_LANG Lang=%1$s";
68     private static final String IMAP_CLOSE_NUT = "CLOSE_NUT";
69 
70     private static final String ISO639_Spanish = "es";
71 
72     /**
73      * For VVM3, if the STATUS SMS returns {@link StatusMessage#getProvisioningStatus()} of {@link
74      * OmtpConstants#SUBSCRIBER_UNKNOWN} and {@link StatusMessage#getReturnCode()} of this value,
75      * the user can self-provision visual voicemail service. For other response codes, the user must
76      * contact customer support to resolve the issue.
77      */
78     private static final String VVM3_UNKNOWN_SUBSCRIBER_CAN_SUBSCRIBE_RESPONSE_CODE = "2";
79 
80     // Default prompt level when using the telephone user interface.
81     // Standard prompt when the user call into the voicemail, and no prompts when someone else is
82     // leaving a voicemail.
83     private static final String VVM3_VM_LANGUAGE_ENGLISH_STANDARD_NO_GUEST_PROMPTS = "5";
84     private static final String VVM3_VM_LANGUAGE_SPANISH_STANDARD_NO_GUEST_PROMPTS = "6";
85 
86     private static final int DEFAULT_PIN_LENGTH = 6;
87 
88     @Override
startActivation(OmtpVvmCarrierConfigHelper config, @Nullable PendingIntent sentIntent)89     public void startActivation(OmtpVvmCarrierConfigHelper config,
90             @Nullable PendingIntent sentIntent) {
91         // VVM3 does not support activation SMS.
92         // Send a status request which will start the provisioning process if the user is not
93         // provisioned.
94         VvmLog.i(TAG, "Activating");
95         config.requestStatus(sentIntent);
96     }
97 
98     @Override
startDeactivation(OmtpVvmCarrierConfigHelper config)99     public void startDeactivation(OmtpVvmCarrierConfigHelper config) {
100         // VVM3 does not support deactivation.
101         // do nothing.
102     }
103 
104     @Override
supportsProvisioning()105     public boolean supportsProvisioning() {
106         return true;
107     }
108 
109     @Override
startProvisioning(ActivationTask task, PhoneAccountHandle phoneAccountHandle, OmtpVvmCarrierConfigHelper config, VoicemailStatus.Editor status, StatusMessage message, Bundle data)110     public void startProvisioning(ActivationTask task, PhoneAccountHandle phoneAccountHandle,
111             OmtpVvmCarrierConfigHelper config, VoicemailStatus.Editor status, StatusMessage message,
112             Bundle data) {
113         VvmLog.i(TAG, "start vvm3 provisioning");
114         if (OmtpConstants.SUBSCRIBER_UNKNOWN.equals(message.getProvisioningStatus())) {
115             VvmLog.i(TAG, "Provisioning status: Unknown");
116             if (VVM3_UNKNOWN_SUBSCRIBER_CAN_SUBSCRIBE_RESPONSE_CODE
117                     .equals(message.getReturnCode())) {
118                 VvmLog.i(TAG, "Self provisioning available, subscribing");
119                 new Vvm3Subscriber(task, phoneAccountHandle, config, status, data).subscribe();
120             } else {
121                 config.handleEvent(status, OmtpEvents.VVM3_SUBSCRIBER_UNKNOWN);
122                 PhoneGlobals.getInstance().setShouldCheckVisualVoicemailConfigurationForMwi(task.getSubId(),
123                         false);
124             }
125         } else if (OmtpConstants.SUBSCRIBER_NEW.equals(message.getProvisioningStatus())) {
126             VvmLog.i(TAG, "setting up new user");
127             // Save the IMAP credentials in preferences so they are persistent and can be retrieved.
128             VisualVoicemailPreferences prefs =
129                     new VisualVoicemailPreferences(config.getContext(), phoneAccountHandle);
130             message.putStatus(prefs.edit()).apply();
131 
132             startProvisionNewUser(task, phoneAccountHandle, config, status, message);
133         } else if (OmtpConstants.SUBSCRIBER_PROVISIONED.equals(message.getProvisioningStatus())) {
134             VvmLog.i(TAG, "User provisioned but not activated, disabling VVM");
135             VisualVoicemailSettingsUtil
136                     .setEnabled(config.getContext(), phoneAccountHandle, false);
137             PhoneGlobals.getInstance().setShouldCheckVisualVoicemailConfigurationForMwi(task.getSubId(),
138                     false);
139         } else if (OmtpConstants.SUBSCRIBER_BLOCKED.equals(message.getProvisioningStatus())) {
140             VvmLog.i(TAG, "User blocked");
141             config.handleEvent(status, OmtpEvents.VVM3_SUBSCRIBER_BLOCKED);
142             PhoneGlobals.getInstance().setShouldCheckVisualVoicemailConfigurationForMwi(task.getSubId(),
143                     false);
144         }
145     }
146 
147     @Override
createMessageSender(SmsManager smsManager, short applicationPort, String destinationNumber)148     public OmtpMessageSender createMessageSender(SmsManager smsManager, short applicationPort,
149             String destinationNumber) {
150         return new Vvm3MessageSender(smsManager, applicationPort, destinationNumber);
151     }
152 
153     @Override
handleEvent(Context context, OmtpVvmCarrierConfigHelper config, VoicemailStatus.Editor status, OmtpEvents event)154     public void handleEvent(Context context, OmtpVvmCarrierConfigHelper config,
155             VoicemailStatus.Editor status, OmtpEvents event) {
156         Vvm3EventHandler.handleEvent(context, config, status, event);
157     }
158 
159     @Override
getCommand(String command)160     public String getCommand(String command) {
161         if (command == OmtpConstants.IMAP_CHANGE_TUI_PWD_FORMAT) {
162             return IMAP_CHANGE_TUI_PWD_FORMAT;
163         }
164         if (command == OmtpConstants.IMAP_CLOSE_NUT) {
165             return IMAP_CLOSE_NUT;
166         }
167         if (command == OmtpConstants.IMAP_CHANGE_VM_LANG_FORMAT) {
168             return IMAP_CHANGE_VM_LANG_FORMAT;
169         }
170         return super.getCommand(command);
171     }
172 
173     @Override
translateStatusSmsBundle(OmtpVvmCarrierConfigHelper config, String event, Bundle data)174     public Bundle translateStatusSmsBundle(OmtpVvmCarrierConfigHelper config, String event,
175             Bundle data) {
176         // UNRECOGNIZED?cmd=STATUS is the response of a STATUS request when the user is provisioned
177         // with iPhone visual voicemail without VoLTE. Translate it into an unprovisioned status
178         // so provisioning can be done.
179         if (!SMS_EVENT_UNRECOGNIZED.equals(event)) {
180             return null;
181         }
182         if (!SMS_EVENT_UNRECOGNIZED_STATUS.equals(data.getString(SMS_EVENT_UNRECOGNIZED_CMD))) {
183             return null;
184         }
185         Bundle bundle = new Bundle();
186         bundle.putString(OmtpConstants.PROVISIONING_STATUS, OmtpConstants.SUBSCRIBER_UNKNOWN);
187         bundle.putString(OmtpConstants.RETURN_CODE,
188                 VVM3_UNKNOWN_SUBSCRIBER_CAN_SUBSCRIBE_RESPONSE_CODE);
189         String vmgUrl = config.getString(DEFAULT_VMG_URL_KEY);
190         if (TextUtils.isEmpty(vmgUrl)) {
191             VvmLog.e(TAG, "Unable to translate STATUS SMS: VMG URL is not set in config");
192             return null;
193         }
194         bundle.putString(Vvm3Subscriber.VMG_URL_KEY, vmgUrl);
195         VvmLog.i(TAG, "UNRECOGNIZED?cmd=STATUS translated into unprovisioned STATUS SMS");
196         return bundle;
197     }
198 
startProvisionNewUser(ActivationTask task, PhoneAccountHandle phoneAccountHandle, OmtpVvmCarrierConfigHelper config, VoicemailStatus.Editor status, StatusMessage message)199     private void startProvisionNewUser(ActivationTask task, PhoneAccountHandle phoneAccountHandle,
200             OmtpVvmCarrierConfigHelper config, VoicemailStatus.Editor status,
201             StatusMessage message) {
202         try (NetworkWrapper wrapper = VvmNetworkRequest
203                 .getNetwork(config, phoneAccountHandle, status)) {
204             Network network = wrapper.get();
205 
206             VvmLog.i(TAG, "new user: network available");
207             try (ImapHelper helper = new ImapHelper(config.getContext(), phoneAccountHandle,
208                     network, status)) {
209                 // VVM3 has inconsistent error language code to OMTP. Just issue a raw command
210                 // here.
211                 // TODO(b/29082671): use LocaleList
212                 if (Locale.getDefault().getLanguage()
213                         .equals(new Locale(ISO639_Spanish).getLanguage())) {
214                     // Spanish
215                     helper.changeVoicemailTuiLanguage(
216                             VVM3_VM_LANGUAGE_SPANISH_STANDARD_NO_GUEST_PROMPTS);
217                 } else {
218                     // English
219                     helper.changeVoicemailTuiLanguage(
220                             VVM3_VM_LANGUAGE_ENGLISH_STANDARD_NO_GUEST_PROMPTS);
221                 }
222                 VvmLog.i(TAG, "new user: language set");
223 
224                 if (setPin(config.getContext(), phoneAccountHandle, helper, message)) {
225                     // Only close new user tutorial if the PIN has been changed.
226                     helper.closeNewUserTutorial();
227                     VvmLog.i(TAG, "new user: NUT closed");
228 
229                     config.requestStatus(null);
230                 }
231             } catch (InitializingException | MessagingException | IOException e) {
232                 config.handleEvent(status, OmtpEvents.VVM3_NEW_USER_SETUP_FAILED);
233                 task.fail();
234                 VvmLog.e(TAG, e.toString());
235             }
236         } catch (RequestFailedException e) {
237             config.handleEvent(status, OmtpEvents.DATA_NO_CONNECTION_CELLULAR_REQUIRED);
238             task.fail();
239         }
240 
241     }
242 
243 
setPin(Context context, PhoneAccountHandle phoneAccountHandle, ImapHelper helper, StatusMessage message)244     private static boolean setPin(Context context, PhoneAccountHandle phoneAccountHandle,
245             ImapHelper helper, StatusMessage message)
246             throws IOException, MessagingException {
247         String defaultPin = getDefaultPin(message);
248         if (defaultPin == null) {
249             VvmLog.i(TAG, "cannot generate default PIN");
250             return false;
251         }
252 
253         if (VoicemailChangePinActivity.isDefaultOldPinSet(context, phoneAccountHandle)) {
254             // The pin was already set
255             VvmLog.i(TAG, "PIN already set");
256             return true;
257         }
258         String newPin = generatePin(getMinimumPinLength(context, phoneAccountHandle));
259         if (helper.changePin(defaultPin, newPin) == OmtpConstants.CHANGE_PIN_SUCCESS) {
260             VoicemailChangePinActivity.setDefaultOldPIN(context, phoneAccountHandle, newPin);
261             helper.handleEvent(OmtpEvents.CONFIG_DEFAULT_PIN_REPLACED);
262         }
263         VvmLog.i(TAG, "new user: PIN set");
264         return true;
265     }
266 
267     @Nullable
getDefaultPin(StatusMessage message)268     private static String getDefaultPin(StatusMessage message) {
269         // The IMAP username is [phone number]@example.com
270         String username = message.getImapUserName();
271         try {
272             String number = username.substring(0, username.indexOf('@'));
273             if (number.length() < 4) {
274                 VvmLog.e(TAG, "unable to extract number from IMAP username");
275                 return null;
276             }
277             return "1" + number.substring(number.length() - 4);
278         } catch (StringIndexOutOfBoundsException e) {
279             VvmLog.e(TAG, "unable to extract number from IMAP username");
280             return null;
281         }
282 
283     }
284 
getMinimumPinLength(Context context, PhoneAccountHandle phoneAccountHandle)285     private static int getMinimumPinLength(Context context, PhoneAccountHandle phoneAccountHandle) {
286         VisualVoicemailPreferences preferences = new VisualVoicemailPreferences(context,
287                 phoneAccountHandle);
288         // The OMTP pin length format is {min}-{max}
289         String[] lengths = preferences.getString(OmtpConstants.TUI_PASSWORD_LENGTH, "").split("-");
290         if (lengths.length == 2) {
291             try {
292                 return Integer.parseInt(lengths[0]);
293             } catch (NumberFormatException e) {
294                 return DEFAULT_PIN_LENGTH;
295             }
296         }
297         return DEFAULT_PIN_LENGTH;
298     }
299 
generatePin(int length)300     private static String generatePin(int length) {
301         SecureRandom random = new SecureRandom();
302         return String.format(Locale.US, "%010d", Math.abs(random.nextLong()))
303                 .substring(0, length);
304 
305     }
306 }
307