blob: 7de81e6854459d25d510fd57618154fc2f31253c [file] [log] [blame]
Eric Erfanianccca3152017-02-22 16:32:36 -08001/*
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
17package com.android.voicemailomtp;
18
19import android.annotation.TargetApi;
20import android.content.Context;
21import android.content.Intent;
22import android.database.ContentObserver;
23import android.os.Build.VERSION_CODES;
24import android.os.Bundle;
25import android.provider.Settings;
26import android.provider.Settings.Global;
27import android.support.annotation.Nullable;
28import android.support.annotation.WorkerThread;
29import android.telecom.PhoneAccountHandle;
30import android.telephony.ServiceState;
31import android.telephony.TelephonyManager;
32import com.android.voicemailomtp.protocol.VisualVoicemailProtocol;
33import com.android.voicemailomtp.scheduling.BaseTask;
34import com.android.voicemailomtp.scheduling.RetryPolicy;
35import com.android.voicemailomtp.sms.StatusMessage;
36import com.android.voicemailomtp.sms.StatusSmsFetcher;
37import com.android.voicemailomtp.sync.OmtpVvmSourceManager;
38import com.android.voicemailomtp.sync.OmtpVvmSyncService;
39import com.android.voicemailomtp.sync.SyncTask;
40import java.io.IOException;
41import java.util.HashSet;
42import java.util.Set;
43import java.util.concurrent.CancellationException;
44import java.util.concurrent.ExecutionException;
45import java.util.concurrent.TimeoutException;
46
47/**
48 * Task to activate the visual voicemail service. A request to activate VVM will be sent to the
49 * carrier, which will respond with a STATUS SMS. The credentials will be updated from the SMS. If
50 * the user is not provisioned provisioning will be attempted. Activation happens when the phone
51 * boots, the SIM is inserted, signal returned when VVM is not activated yet, and when the carrier
52 * spontaneously sent a STATUS SMS.
53 */
54@TargetApi(VERSION_CODES.CUR_DEVELOPMENT)
55public class ActivationTask extends BaseTask {
56
57 private static final String TAG = "VvmActivationTask";
58
59 private static final int RETRY_TIMES = 4;
60 private static final int RETRY_INTERVAL_MILLIS = 5_000;
61
62 private static final String EXTRA_MESSAGE_DATA_BUNDLE = "extra_message_data_bundle";
63
64 @Nullable
65 private static DeviceProvisionedObserver sDeviceProvisionedObserver;
66
67 private final RetryPolicy mRetryPolicy;
68
69 private Bundle mMessageData;
70
71 public ActivationTask() {
72 super(TASK_ACTIVATION);
73 mRetryPolicy = new RetryPolicy(RETRY_TIMES, RETRY_INTERVAL_MILLIS);
74 addPolicy(mRetryPolicy);
75 }
76
77 /**
78 * Has the user gone through the setup wizard yet.
79 */
80 private static boolean isDeviceProvisioned(Context context) {
81 return Settings.Global.getInt(
82 context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) == 1;
83 }
84
85 /**
86 * @param messageData The optional bundle from {@link android.provider.VoicemailContract#
87 * EXTRA_VOICEMAIL_SMS_FIELDS}, if the task is initiated by a status SMS. If null the task will
88 * request a status SMS itself.
89 */
90 public static void start(Context context, PhoneAccountHandle phoneAccountHandle,
91 @Nullable Bundle messageData) {
92 if (!isDeviceProvisioned(context)) {
93 VvmLog.i(TAG, "Activation requested while device is not provisioned, postponing");
94 // Activation might need information such as system language to be set, so wait until
95 // the setup wizard is finished. The data bundle from the SMS will be re-requested upon
96 // activation.
97 queueActivationAfterProvisioned(context, phoneAccountHandle);
98 return;
99 }
100
101 Intent intent = BaseTask.createIntent(context, ActivationTask.class, phoneAccountHandle);
102 if (messageData != null) {
103 intent.putExtra(EXTRA_MESSAGE_DATA_BUNDLE, messageData);
104 }
105 context.startService(intent);
106 }
107
108 public void onCreate(Context context, Intent intent, int flags, int startId) {
109 super.onCreate(context, intent, flags, startId);
110 mMessageData = intent.getParcelableExtra(EXTRA_MESSAGE_DATA_BUNDLE);
111 }
112
113 @Override
114 public Intent createRestartIntent() {
115 Intent intent = super.createRestartIntent();
116 // mMessageData is discarded, request a fresh STATUS SMS for retries.
117 return intent;
118 }
119
120 @Override
121 @WorkerThread
122 public void onExecuteInBackgroundThread() {
123 Assert.isNotMainThread();
124
125 PhoneAccountHandle phoneAccountHandle = getPhoneAccountHandle();
126 if (phoneAccountHandle == null) {
127 // This should never happen
128 VvmLog.e(TAG, "null PhoneAccountHandle");
129 return;
130 }
131
132 OmtpVvmCarrierConfigHelper helper =
133 new OmtpVvmCarrierConfigHelper(getContext(), phoneAccountHandle);
134 if (!helper.isValid()) {
135 VvmLog.i(TAG, "VVM not supported on phoneAccountHandle " + phoneAccountHandle);
136 VoicemailStatus.disable(getContext(), phoneAccountHandle);
137 return;
138 }
139
140 // OmtpVvmCarrierConfigHelper can start the activation process; it will pass in a vvm
141 // content provider URI which we will use. On some occasions, setting that URI will
142 // fail, so we will perform a few attempts to ensure that the vvm content provider has
143 // a good chance of being started up.
144 if (!VoicemailStatus.edit(getContext(), phoneAccountHandle)
145 .setType(helper.getVvmType())
146 .apply()) {
147 VvmLog.e(TAG, "Failed to configure content provider - " + helper.getVvmType());
148 fail();
149 }
150 VvmLog.i(TAG, "VVM content provider configured - " + helper.getVvmType());
151
152 if (!OmtpVvmSourceManager.getInstance(getContext())
153 .isVvmSourceRegistered(phoneAccountHandle)) {
154 // This account has not been activated before during the lifetime of this boot.
155 VisualVoicemailPreferences preferences = new VisualVoicemailPreferences(getContext(),
156 phoneAccountHandle);
157 if (preferences.getString(OmtpConstants.SERVER_ADDRESS, null) == null) {
158 // Only show the "activating" message if activation has not been completed before.
159 // Subsequent activations are more of a status check and usually does not
160 // concern the user.
161 helper.handleEvent(VoicemailStatus.edit(getContext(), phoneAccountHandle),
162 OmtpEvents.CONFIG_ACTIVATING);
163 } else {
164 // The account has been activated on this device before. Pretend it is already
165 // activated. If there are any activation error it will overwrite this status.
166 helper.handleEvent(VoicemailStatus.edit(getContext(), phoneAccountHandle),
167 OmtpEvents.CONFIG_ACTIVATING_SUBSEQUENT);
168 }
169
170 }
171 if (!hasSignal(getContext(), phoneAccountHandle)) {
172 VvmLog.i(TAG, "Service lost during activation, aborting");
173 // Restore the "NO SIGNAL" state since it will be overwritten by the CONFIG_ACTIVATING
174 // event.
175 helper.handleEvent(VoicemailStatus.edit(getContext(), phoneAccountHandle),
176 OmtpEvents.NOTIFICATION_SERVICE_LOST);
177 // Don't retry, a new activation will be started after the signal returned.
178 return;
179 }
180
181 helper.activateSmsFilter();
182 VoicemailStatus.Editor status = mRetryPolicy.getVoicemailStatusEditor();
183
184 VisualVoicemailProtocol protocol = helper.getProtocol();
185
186 Bundle data;
187 if (mMessageData != null) {
188 // The content of STATUS SMS is provided to launch this task, no need to request it
189 // again.
190 data = mMessageData;
191 } else {
192 try (StatusSmsFetcher fetcher = new StatusSmsFetcher(getContext(),
193 phoneAccountHandle)) {
194 protocol.startActivation(helper, fetcher.getSentIntent());
195 // Both the fetcher and OmtpMessageReceiver will be triggered, but
196 // OmtpMessageReceiver will just route the SMS back to ActivationTask, which will be
197 // rejected because the task is still running.
198 data = fetcher.get();
199 } catch (TimeoutException e) {
200 // The carrier is expected to return an STATUS SMS within STATUS_SMS_TIMEOUT_MILLIS
201 // handleEvent() will do the logging.
202 helper.handleEvent(status, OmtpEvents.CONFIG_STATUS_SMS_TIME_OUT);
203 fail();
204 return;
205 } catch (CancellationException e) {
206 VvmLog.e(TAG, "Unable to send status request SMS");
207 fail();
208 return;
209 } catch (InterruptedException | ExecutionException | IOException e) {
210 VvmLog.e(TAG, "can't get future STATUS SMS", e);
211 fail();
212 return;
213 }
214 }
215
216 StatusMessage message = new StatusMessage(data);
217 VvmLog.d(TAG, "STATUS SMS received: st=" + message.getProvisioningStatus()
218 + ", rc=" + message.getReturnCode());
219
220 if (message.getProvisioningStatus().equals(OmtpConstants.SUBSCRIBER_READY)) {
221 VvmLog.d(TAG, "subscriber ready, no activation required");
222 updateSource(getContext(), phoneAccountHandle, status, message);
223 } else {
224 if (helper.supportsProvisioning()) {
225 VvmLog.i(TAG, "Subscriber not ready, start provisioning");
226 helper.startProvisioning(this, phoneAccountHandle, status, message, data);
227
228 } else if (message.getProvisioningStatus().equals(OmtpConstants.SUBSCRIBER_NEW)) {
229 VvmLog.i(TAG, "Subscriber new but provisioning is not supported");
230 // Ignore the non-ready state and attempt to use the provided info as is.
231 // This is probably caused by not completing the new user tutorial.
232 updateSource(getContext(), phoneAccountHandle, status, message);
233 } else {
234 VvmLog.i(TAG, "Subscriber not ready but provisioning is not supported");
235 helper.handleEvent(status, OmtpEvents.CONFIG_SERVICE_NOT_AVAILABLE);
236 }
237 }
238 }
239
240 public static void updateSource(Context context, PhoneAccountHandle phone,
241 VoicemailStatus.Editor status, StatusMessage message) {
242 OmtpVvmSourceManager vvmSourceManager =
243 OmtpVvmSourceManager.getInstance(context);
244
245 if (OmtpConstants.SUCCESS.equals(message.getReturnCode())) {
246 OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(context, phone);
247 helper.handleEvent(status, OmtpEvents.CONFIG_REQUEST_STATUS_SUCCESS);
248
249 // Save the IMAP credentials in preferences so they are persistent and can be retrieved.
250 VisualVoicemailPreferences prefs = new VisualVoicemailPreferences(context, phone);
251 message.putStatus(prefs.edit()).apply();
252
253 // Add the source to indicate that it is active.
254 vvmSourceManager.addSource(phone);
255
256 SyncTask.start(context, phone, OmtpVvmSyncService.SYNC_FULL_SYNC);
257 } else {
258 VvmLog.e(TAG, "Visual voicemail not available for subscriber.");
259 }
260 }
261
262 private static boolean hasSignal(Context context, PhoneAccountHandle phoneAccountHandle) {
263 TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class)
264 .createForPhoneAccountHandle(phoneAccountHandle);
265 return telephonyManager.getServiceState().getState() == ServiceState.STATE_IN_SERVICE;
266 }
267
268 private static void queueActivationAfterProvisioned(Context context,
269 PhoneAccountHandle phoneAccountHandle) {
270 if (sDeviceProvisionedObserver == null) {
271 sDeviceProvisionedObserver = new DeviceProvisionedObserver(context);
272 context.getContentResolver()
273 .registerContentObserver(Settings.Global.getUriFor(Global.DEVICE_PROVISIONED),
274 false, sDeviceProvisionedObserver);
275 }
276 sDeviceProvisionedObserver.addPhoneAcountHandle(phoneAccountHandle);
277 }
278
279 private static class DeviceProvisionedObserver extends ContentObserver {
280
281 private final Context mContext;
282 private final Set<PhoneAccountHandle> mPhoneAccountHandles = new HashSet<>();
283
284 private DeviceProvisionedObserver(Context context) {
285 super(null);
286 mContext = context;
287 }
288
289 public void addPhoneAcountHandle(PhoneAccountHandle phoneAccountHandle) {
290 mPhoneAccountHandles.add(phoneAccountHandle);
291 }
292
293 @Override
294 public void onChange(boolean selfChange) {
295 if (isDeviceProvisioned(mContext)) {
296 VvmLog.i(TAG, "device provisioned, resuming activation");
297 for (PhoneAccountHandle phoneAccountHandle : mPhoneAccountHandles) {
298 start(mContext, phoneAccountHandle, null);
299 }
300 mContext.getContentResolver().unregisterContentObserver(sDeviceProvisionedObserver);
301 sDeviceProvisionedObserver = null;
302 }
303 }
304 }
305}