1d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian/*
2d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * Copyright (C) 2016 The Android Open Source Project
3d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian *
4d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * Licensed under the Apache License, Version 2.0 (the "License");
5d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * you may not use this file except in compliance with the License.
6d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * You may obtain a copy of the License at
7d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian *
8d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian *      http://www.apache.org/licenses/LICENSE-2.0
9d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian *
10d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * Unless required by applicable law or agreed to in writing, software
11d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * distributed under the License is distributed on an "AS IS" BASIS,
12d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * See the License for the specific language governing permissions and
14d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * limitations under the License
15d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian */
16d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
17d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianpackage com.android.voicemail.impl.protocol;
18d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
19d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport android.annotation.TargetApi;
2010b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanianimport android.content.Context;
21d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport android.net.Network;
22d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport android.os.Build;
23d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport android.os.Build.VERSION_CODES;
24d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport android.os.Bundle;
2510b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanianimport android.support.annotation.NonNull;
2610b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanianimport android.support.annotation.VisibleForTesting;
27d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport android.support.annotation.WorkerThread;
28d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport android.telecom.PhoneAccountHandle;
29d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport android.telephony.TelephonyManager;
30d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport android.text.Html;
31d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport android.text.Spanned;
32d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport android.text.style.URLSpan;
33d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport android.util.ArrayMap;
342f1c7586bcce334ca69022eb8dc6d8965ceb6a05Eric Erfanianimport com.android.dialer.configprovider.ConfigProviderBindings;
35d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.voicemail.impl.ActivationTask;
36d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.voicemail.impl.Assert;
37d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.voicemail.impl.OmtpEvents;
38d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.voicemail.impl.OmtpVvmCarrierConfigHelper;
39d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.voicemail.impl.VoicemailStatus;
40d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.voicemail.impl.VvmLog;
41d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.voicemail.impl.sync.VvmNetworkRequest;
42d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.voicemail.impl.sync.VvmNetworkRequest.NetworkWrapper;
43d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.voicemail.impl.sync.VvmNetworkRequest.RequestFailedException;
44d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.volley.AuthFailureError;
45d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.volley.Request;
46d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.volley.RequestQueue;
47d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.volley.toolbox.HurlStack;
48d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.volley.toolbox.RequestFuture;
49d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.volley.toolbox.StringRequest;
50d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.volley.toolbox.Volley;
51d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.io.IOException;
52d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.net.CookieHandler;
53d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.net.CookieManager;
54d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.net.HttpURLConnection;
55d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.net.URL;
5610b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanianimport java.util.ArrayList;
5710b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanianimport java.util.List;
58d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.util.Locale;
59d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.util.Map;
60d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.util.Random;
61d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.util.concurrent.ExecutionException;
62d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.util.concurrent.TimeUnit;
63d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.util.concurrent.TimeoutException;
64d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.util.regex.Matcher;
65d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.util.regex.Pattern;
6610b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanianimport org.json.JSONArray;
6710b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanianimport org.json.JSONException;
68d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
69d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian/**
70d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * Class to subscribe to basic VVM3 visual voicemail, for example, Verizon. Subscription is required
71d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * when the user is unprovisioned. This could happen when the user is on a legacy service, or
72d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * switched over from devices that used other type of visual voicemail.
73d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian *
74d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * <p>The STATUS SMS will come with a URL to the voicemail management gateway. From it we can find
75d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * the self provisioning gateway URL that we can modify voicemail services.
76d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian *
77d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * <p>A request to the self provisioning gateway to activate basic visual voicemail will return us
78d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * with a web page. If the user hasn't subscribe to it yet it will contain a link to confirm the
79d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * subscription. This link should be clicked through cellular network, and have cookies enabled.
80d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian *
81d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * <p>After the process is completed, the carrier should send us another STATUS SMS with a new or
82d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * ready user.
83d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian */
84d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian@TargetApi(VERSION_CODES.O)
85d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianpublic class Vvm3Subscriber {
86d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
87d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static final String TAG = "Vvm3Subscriber";
88d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
89d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static final String OPERATION_GET_SPG_URL = "retrieveSPGURL";
90d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static final String SPG_URL_TAG = "spgurl";
91d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static final String TRANSACTION_ID_TAG = "transactionid";
92d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  //language=XML
93d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static final String VMG_XML_REQUEST_FORMAT =
94d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      ""
95d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
96d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          + "<VMGVVMRequest>"
97d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          + "  <MessageHeader>"
98d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          + "    <transactionid>%1$s</transactionid>"
99d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          + "  </MessageHeader>"
100d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          + "  <MessageBody>"
101d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          + "    <mdn>%2$s</mdn>"
102d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          + "    <operation>%3$s</operation>"
103d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          + "    <source>Device</source>"
104d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          + "    <devicemodel>%4$s</devicemodel>"
105d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          + "  </MessageBody>"
106d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          + "</VMGVVMRequest>";
107d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
108d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  static final String VMG_URL_KEY = "vmg_url";
109d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
110d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  // Self provisioning POST key/values. VVM3 API 2.1.0 12.3
111d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static final String SPG_VZW_MDN_PARAM = "VZW_MDN";
112d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static final String SPG_VZW_SERVICE_PARAM = "VZW_SERVICE";
113d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static final String SPG_VZW_SERVICE_BASIC = "BVVM";
114d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static final String SPG_DEVICE_MODEL_PARAM = "DEVICE_MODEL";
115d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  // Value for all android device
116d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static final String SPG_DEVICE_MODEL_ANDROID = "DROID_4G";
117d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static final String SPG_APP_TOKEN_PARAM = "APP_TOKEN";
118d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static final String SPG_APP_TOKEN = "q8e3t5u2o1";
119d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static final String SPG_LANGUAGE_PARAM = "SPG_LANGUAGE_PARAM";
120d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static final String SPG_LANGUAGE_EN = "ENGLISH";
121d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
12210b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian  @VisibleForTesting
12310b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian  static final String VVM3_SUBSCRIBE_LINK_PATTERNS_JSON_ARRAY =
12410b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian      "vvm3_subscribe_link_pattern_json_array";
12510b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian
12610b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian  private static final String VVM3_SUBSCRIBE_LINK_DEFAULT_PATTERNS =
12710b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian      "["
12810b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian          + "\"(?i)Subscribe to Basic Visual Voice Mail\","
12910b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian          + "\"(?i)Subscribe to Basic Visual Voicemail\""
13010b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian          + "]";
131d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
132d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static final int REQUEST_TIMEOUT_SECONDS = 30;
133d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
134d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private final ActivationTask mTask;
135d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private final PhoneAccountHandle mHandle;
136d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private final OmtpVvmCarrierConfigHelper mHelper;
137d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private final VoicemailStatus.Editor mStatus;
138d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private final Bundle mData;
139d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
140d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private final String mNumber;
141d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
142d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private RequestQueue mRequestQueue;
143d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
14410b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian  @VisibleForTesting
14510b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian  static class ProvisioningException extends Exception {
146d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
147d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    public ProvisioningException(String message) {
148d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      super(message);
149d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
150d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
151d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
152d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  static {
153d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    // Set the default cookie handler to retain session data for the self provisioning gateway.
154d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    // Note; this is not ideal as it is application-wide, and can easily get clobbered.
155d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    // But it seems to be the preferred way to manage cookie for HttpURLConnection, and manually
156d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    // managing cookies will greatly increase complexity.
157d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    CookieManager cookieManager = new CookieManager();
158d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    CookieHandler.setDefault(cookieManager);
159d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
160d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
161d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  @WorkerThread
162d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  public Vvm3Subscriber(
163d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      ActivationTask task,
164d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      PhoneAccountHandle handle,
165d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      OmtpVvmCarrierConfigHelper helper,
166d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      VoicemailStatus.Editor status,
167d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      Bundle data) {
168d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    Assert.isNotMainThread();
169d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    mTask = task;
170d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    mHandle = handle;
171d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    mHelper = helper;
172d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    mStatus = status;
173d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    mData = data;
174d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
175d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    // Assuming getLine1Number() will work with VVM3. For unprovisioned users the IMAP username
176d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    // is not included in the status SMS, thus no other way to get the current phone number.
177d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    mNumber =
178d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        mHelper
179d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian            .getContext()
180d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian            .getSystemService(TelephonyManager.class)
181d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian            .createForPhoneAccountHandle(mHandle)
182d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian            .getLine1Number();
183d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
184d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
185d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  @WorkerThread
186d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  public void subscribe() {
187d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    Assert.isNotMainThread();
188d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    // Cellular data is required to subscribe.
189d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    // processSubscription() is called after network is available.
190d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    VvmLog.i(TAG, "Subscribing");
191d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
192d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    try (NetworkWrapper wrapper = VvmNetworkRequest.getNetwork(mHelper, mHandle, mStatus)) {
193d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      Network network = wrapper.get();
194d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      VvmLog.d(TAG, "provisioning: network available");
195d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      mRequestQueue =
196d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          Volley.newRequestQueue(mHelper.getContext(), new NetworkSpecifiedHurlStack(network));
197d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      processSubscription();
198d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    } catch (RequestFailedException e) {
199d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      mHelper.handleEvent(mStatus, OmtpEvents.VVM3_VMG_CONNECTION_FAILED);
200d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      mTask.fail();
201d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
202d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
203d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
204d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private void processSubscription() {
205d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    try {
206d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      String gatewayUrl = getSelfProvisioningGateway();
207d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      String selfProvisionResponse = getSelfProvisionResponse(gatewayUrl);
20810b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian      String subscribeLink =
20910b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian          findSubscribeLink(getSubscribeLinkPatterns(mHelper.getContext()), selfProvisionResponse);
210d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      clickSubscribeLink(subscribeLink);
211d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    } catch (ProvisioningException e) {
212d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      VvmLog.e(TAG, e.toString());
213d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      mTask.fail();
214d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
215d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
216d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
217d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  /** Get the URL to perform self-provisioning from the voicemail management gateway. */
218d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private String getSelfProvisioningGateway() throws ProvisioningException {
219d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    VvmLog.i(TAG, "retrieving SPG URL");
220d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    String response = vvm3XmlRequest(OPERATION_GET_SPG_URL);
221d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    return extractText(response, SPG_URL_TAG);
222d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
223d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
224d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  /**
225d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * Sent a request to the self-provisioning gateway, which will return us with a webpage. The page
226d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * might contain a "Subscribe to Basic Visual Voice Mail" link to complete the subscription. The
227d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * cookie from this response and cellular data is required to click the link.
228d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   */
229d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private String getSelfProvisionResponse(String url) throws ProvisioningException {
230d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    VvmLog.i(TAG, "Retrieving self provisioning response");
231d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
232d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    RequestFuture<String> future = RequestFuture.newFuture();
233d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
234d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    StringRequest stringRequest =
235d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        new StringRequest(Request.Method.POST, url, future, future) {
236d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          @Override
237d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          protected Map<String, String> getParams() {
238d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian            Map<String, String> params = new ArrayMap<>();
239d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian            params.put(SPG_VZW_MDN_PARAM, mNumber);
240d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian            params.put(SPG_VZW_SERVICE_PARAM, SPG_VZW_SERVICE_BASIC);
241d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian            params.put(SPG_DEVICE_MODEL_PARAM, SPG_DEVICE_MODEL_ANDROID);
242d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian            params.put(SPG_APP_TOKEN_PARAM, SPG_APP_TOKEN);
243d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian            // Language to display the subscription page. The page is never shown to the user
244d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian            // so just use English.
245d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian            params.put(SPG_LANGUAGE_PARAM, SPG_LANGUAGE_EN);
246d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian            return params;
247d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          }
248d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        };
249d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
250d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    mRequestQueue.add(stringRequest);
251d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    try {
252d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      return future.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
253d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    } catch (InterruptedException | ExecutionException | TimeoutException e) {
254d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      mHelper.handleEvent(mStatus, OmtpEvents.VVM3_SPG_CONNECTION_FAILED);
255d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      throw new ProvisioningException(e.toString());
256d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
257d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
258d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
259d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private void clickSubscribeLink(String subscribeLink) throws ProvisioningException {
260d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    VvmLog.i(TAG, "Clicking subscribe link");
261d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    RequestFuture<String> future = RequestFuture.newFuture();
262d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
263d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    StringRequest stringRequest =
264d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        new StringRequest(Request.Method.POST, subscribeLink, future, future);
265d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    mRequestQueue.add(stringRequest);
266d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    try {
267d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      // A new STATUS SMS will be sent after this request.
268d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      future.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
269d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    } catch (TimeoutException | ExecutionException | InterruptedException e) {
270d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      mHelper.handleEvent(mStatus, OmtpEvents.VVM3_SPG_CONNECTION_FAILED);
271d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      throw new ProvisioningException(e.toString());
272d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
273d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    // It could take very long for the STATUS SMS to return. Waiting for it is unreliable.
274d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    // Just leave the CONFIG STATUS as CONFIGURING and end the task. The user can always
275d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    // manually retry if it took too long.
276d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
277d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
278d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private String vvm3XmlRequest(String operation) throws ProvisioningException {
279d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    VvmLog.d(TAG, "Sending vvm3XmlRequest for " + operation);
280d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    String voicemailManagementGateway = mData.getString(VMG_URL_KEY);
281d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    if (voicemailManagementGateway == null) {
282d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      VvmLog.e(TAG, "voicemailManagementGateway url unknown");
283d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      return null;
284d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
285d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    String transactionId = createTransactionId();
286d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    String body =
287d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        String.format(
288d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian            Locale.US, VMG_XML_REQUEST_FORMAT, transactionId, mNumber, operation, Build.MODEL);
289d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
290d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    RequestFuture<String> future = RequestFuture.newFuture();
291d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    StringRequest stringRequest =
292d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        new StringRequest(Request.Method.POST, voicemailManagementGateway, future, future) {
293d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          @Override
294d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          public byte[] getBody() throws AuthFailureError {
295d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian            return body.getBytes();
296d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          }
297d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        };
298d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    mRequestQueue.add(stringRequest);
299d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
300d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    try {
301d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      String response = future.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
302d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      if (!transactionId.equals(extractText(response, TRANSACTION_ID_TAG))) {
303d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        throw new ProvisioningException("transactionId mismatch");
304d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      }
305d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      return response;
306d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    } catch (InterruptedException | ExecutionException | TimeoutException e) {
307d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      mHelper.handleEvent(mStatus, OmtpEvents.VVM3_VMG_CONNECTION_FAILED);
308d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      throw new ProvisioningException(e.toString());
309d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
310d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
311d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
31210b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian  @VisibleForTesting
31310b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian  static List<Pattern> getSubscribeLinkPatterns(Context context) {
31410b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian    String patternsJsonString =
31510b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian        ConfigProviderBindings.get(context)
31610b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian            .getString(
31710b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian                VVM3_SUBSCRIBE_LINK_PATTERNS_JSON_ARRAY, VVM3_SUBSCRIBE_LINK_DEFAULT_PATTERNS);
31810b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian    List<Pattern> patterns = new ArrayList<>();
31910b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian    try {
32010b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian      JSONArray patternsArray = new JSONArray(patternsJsonString);
32110b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian      for (int i = 0; i < patternsArray.length(); i++) {
32210b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian        patterns.add(Pattern.compile(patternsArray.getString(i)));
32310b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian      }
32410b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian    } catch (JSONException e) {
32510b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian      throw new IllegalArgumentException("Unable to parse patterns" + e);
32610b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian    }
32710b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian    return patterns;
32810b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian  }
32910b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian
33010b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian  @VisibleForTesting
33110b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian  static String findSubscribeLink(@NonNull List<Pattern> patterns, String response)
33210b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian      throws ProvisioningException {
33310b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian    if (patterns.isEmpty()) {
33410b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian      throw new IllegalArgumentException("empty patterns");
33510b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian    }
336d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    Spanned doc = Html.fromHtml(response, Html.FROM_HTML_MODE_LEGACY);
337d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    URLSpan[] spans = doc.getSpans(0, doc.length(), URLSpan.class);
338d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    StringBuilder fulltext = new StringBuilder();
33910b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian
340d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    for (URLSpan span : spans) {
341d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      String text = doc.subSequence(doc.getSpanStart(span), doc.getSpanEnd(span)).toString();
34210b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian      for (Pattern pattern : patterns) {
34310b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian        if (pattern.matcher(text).matches()) {
34410b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian          return span.getURL();
34510b34a5ebf12e97ecba0caf3c8e30b476b038a96Eric Erfanian        }
346d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      }
347d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      fulltext.append(text);
348d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
349d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    throw new ProvisioningException("Subscribe link not found: " + fulltext);
350d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
351d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
352d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private String createTransactionId() {
353d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    return String.valueOf(Math.abs(new Random().nextLong()));
354d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
355d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
356d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private String extractText(String xml, String tag) throws ProvisioningException {
357d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    Pattern pattern = Pattern.compile("<" + tag + ">(.*)<\\/" + tag + ">");
358d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    Matcher matcher = pattern.matcher(xml);
359d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    if (matcher.find()) {
360d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      return matcher.group(1);
361d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
362d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    throw new ProvisioningException("Tag " + tag + " not found in xml response");
363d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
364d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
365d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static class NetworkSpecifiedHurlStack extends HurlStack {
366d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
367d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    private final Network mNetwork;
368d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
369d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    public NetworkSpecifiedHurlStack(Network network) {
370d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      mNetwork = network;
371d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
372d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
373d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    @Override
374d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    protected HttpURLConnection createConnection(URL url) throws IOException {
375d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      return (HttpURLConnection) mNetwork.openConnection(url);
376d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
377d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
378d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian}
379