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