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