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