ActivationTask.java revision c857f90590e7d7fcffa89511982eb33afd34805f
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.os.Build.VERSION_CODES;
23import android.os.Bundle;
24import android.provider.Settings;
25import android.support.annotation.Nullable;
26import android.support.annotation.WorkerThread;
27import android.telecom.PhoneAccountHandle;
28import android.telephony.ServiceState;
29import android.telephony.TelephonyManager;
30import com.android.dialer.logging.DialerImpression;
31import com.android.dialer.proguard.UsedByReflection;
32import com.android.voicemail.VoicemailClient;
33import com.android.voicemail.impl.protocol.VisualVoicemailProtocol;
34import com.android.voicemail.impl.scheduling.BaseTask;
35import com.android.voicemail.impl.scheduling.RetryPolicy;
36import com.android.voicemail.impl.settings.VisualVoicemailSettingsUtil;
37import com.android.voicemail.impl.sms.StatusMessage;
38import com.android.voicemail.impl.sms.StatusSmsFetcher;
39import com.android.voicemail.impl.sync.OmtpVvmSyncService;
40import com.android.voicemail.impl.sync.SyncTask;
41import com.android.voicemail.impl.sync.VvmAccountManager;
42import com.android.voicemail.impl.utils.LoggerUtils;
43import java.io.IOException;
44import java.util.concurrent.CancellationException;
45import java.util.concurrent.ExecutionException;
46import java.util.concurrent.TimeoutException;
47
48/**
49 * Task to activate the visual voicemail service. A request to activate VVM will be sent to the
50 * carrier, which will respond with a STATUS SMS. The credentials will be updated from the SMS. If
51 * the user is not provisioned provisioning will be attempted. Activation happens when the phone
52 * boots, the SIM is inserted, signal returned when VVM is not activated yet, and when the carrier
53 * spontaneously sent a STATUS SMS.
54 */
55@TargetApi(VERSION_CODES.O)
56@UsedByReflection(value = "Tasks.java")
57public class ActivationTask extends BaseTask {
58
59  private static final String TAG = "VvmActivationTask";
60
61  private static final int RETRY_TIMES = 4;
62  private static final int RETRY_INTERVAL_MILLIS = 5_000;
63
64  private static final String EXTRA_MESSAGE_DATA_BUNDLE = "extra_message_data_bundle";
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      DeviceProvisionedJobService.activateAfterProvisioned(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.sendBroadcast(intent);
104  }
105
106  @Override
107  public void onCreate(Context context, Bundle extras) {
108    super.onCreate(context, extras);
109    mMessageData = extras.getParcelable(EXTRA_MESSAGE_DATA_BUNDLE);
110  }
111
112  @Override
113  public Intent createRestartIntent() {
114    LoggerUtils.logImpressionOnMainThread(
115        getContext(), DialerImpression.Type.VVM_AUTO_RETRY_ACTIVATION);
116    Intent intent = super.createRestartIntent();
117    // mMessageData is discarded, request a fresh STATUS SMS for retries.
118    return intent;
119  }
120
121  @Override
122  @WorkerThread
123  public void onExecuteInBackgroundThread() {
124    Assert.isNotMainThread();
125    LoggerUtils.logImpressionOnMainThread(
126        getContext(), DialerImpression.Type.VVM_ACTIVATION_STARTED);
127    PhoneAccountHandle phoneAccountHandle = getPhoneAccountHandle();
128    if (phoneAccountHandle == null) {
129      // This should never happen
130      VvmLog.e(TAG, "null PhoneAccountHandle");
131      return;
132    }
133
134    PreOMigrationHandler.migrate(getContext(), phoneAccountHandle);
135
136    if (!VisualVoicemailSettingsUtil.isEnabled(getContext(), phoneAccountHandle)) {
137      VvmLog.i(TAG, "VVM is disabled");
138      return;
139    }
140
141    OmtpVvmCarrierConfigHelper helper =
142        new OmtpVvmCarrierConfigHelper(getContext(), phoneAccountHandle);
143    if (!helper.isValid()) {
144      VvmLog.i(TAG, "VVM not supported on phoneAccountHandle " + phoneAccountHandle);
145      VvmAccountManager.removeAccount(getContext(), phoneAccountHandle);
146      return;
147    }
148
149    // OmtpVvmCarrierConfigHelper can start the activation process; it will pass in a vvm
150    // content provider URI which we will use.  On some occasions, setting that URI will
151    // fail, so we will perform a few attempts to ensure that the vvm content provider has
152    // a good chance of being started up.
153    if (!VoicemailStatus.edit(getContext(), phoneAccountHandle)
154        .setType(helper.getVvmType())
155        .apply()) {
156      VvmLog.e(TAG, "Failed to configure content provider - " + helper.getVvmType());
157      fail();
158    }
159    VvmLog.i(TAG, "VVM content provider configured - " + helper.getVvmType());
160
161    if (VvmAccountManager.isAccountActivated(getContext(), phoneAccountHandle)) {
162      VvmLog.i(TAG, "Account is already activated");
163      onSuccess(getContext(), phoneAccountHandle);
164      return;
165    }
166    helper.handleEvent(
167        VoicemailStatus.edit(getContext(), phoneAccountHandle), OmtpEvents.CONFIG_ACTIVATING);
168
169    if (!hasSignal(getContext(), phoneAccountHandle)) {
170      VvmLog.i(TAG, "Service lost during activation, aborting");
171      // Restore the "NO SIGNAL" state since it will be overwritten by the CONFIG_ACTIVATING
172      // event.
173      helper.handleEvent(
174          VoicemailStatus.edit(getContext(), phoneAccountHandle),
175          OmtpEvents.NOTIFICATION_SERVICE_LOST);
176      // Don't retry, a new activation will be started after the signal returned.
177      return;
178    }
179
180    helper.activateSmsFilter();
181    VoicemailStatus.Editor status = mRetryPolicy.getVoicemailStatusEditor();
182
183    VisualVoicemailProtocol protocol = helper.getProtocol();
184
185    Bundle data;
186    if (mMessageData != null) {
187      // The content of STATUS SMS is provided to launch this task, no need to request it
188      // again.
189      data = mMessageData;
190    } else {
191      try (StatusSmsFetcher fetcher = new StatusSmsFetcher(getContext(), phoneAccountHandle)) {
192        protocol.startActivation(helper, fetcher.getSentIntent());
193        // Both the fetcher and OmtpMessageReceiver will be triggered, but
194        // OmtpMessageReceiver will just route the SMS back to ActivationTask, which will be
195        // rejected because the task is still running.
196        data = fetcher.get();
197      } catch (TimeoutException e) {
198        // The carrier is expected to return an STATUS SMS within STATUS_SMS_TIMEOUT_MILLIS
199        // handleEvent() will do the logging.
200        helper.handleEvent(status, OmtpEvents.CONFIG_STATUS_SMS_TIME_OUT);
201        fail();
202        return;
203      } catch (CancellationException e) {
204        VvmLog.e(TAG, "Unable to send status request SMS");
205        fail();
206        return;
207      } catch (InterruptedException | ExecutionException | IOException e) {
208        VvmLog.e(TAG, "can't get future STATUS SMS", e);
209        fail();
210        return;
211      }
212    }
213
214    StatusMessage message = new StatusMessage(data);
215    VvmLog.d(
216        TAG,
217        "STATUS SMS received: st="
218            + message.getProvisioningStatus()
219            + ", rc="
220            + message.getReturnCode());
221    if (message.getProvisioningStatus().equals(OmtpConstants.SUBSCRIBER_READY)) {
222      VvmLog.d(TAG, "subscriber ready, no activation required");
223      updateSource(getContext(), phoneAccountHandle, message);
224    } else {
225      if (helper.supportsProvisioning()) {
226        VvmLog.i(TAG, "Subscriber not ready, start provisioning");
227        helper.startProvisioning(this, phoneAccountHandle, status, message, data);
228
229      } else if (message.getProvisioningStatus().equals(OmtpConstants.SUBSCRIBER_NEW)) {
230        VvmLog.i(TAG, "Subscriber new but provisioning is not supported");
231        // Ignore the non-ready state and attempt to use the provided info as is.
232        // This is probably caused by not completing the new user tutorial.
233        updateSource(getContext(), phoneAccountHandle, message);
234      } else {
235        VvmLog.i(TAG, "Subscriber not ready but provisioning is not supported");
236        helper.handleEvent(status, OmtpEvents.CONFIG_SERVICE_NOT_AVAILABLE);
237      }
238    }
239    LoggerUtils.logImpressionOnMainThread(
240        getContext(), DialerImpression.Type.VVM_ACTIVATION_COMPLETED);
241  }
242
243  private static void updateSource(
244      Context context, PhoneAccountHandle phone, StatusMessage message) {
245
246    if (OmtpConstants.SUCCESS.equals(message.getReturnCode())) {
247      // Save the IMAP credentials in preferences so they are persistent and can be retrieved.
248      VvmAccountManager.addAccount(context, phone, message);
249      onSuccess(context, phone);
250    } else {
251      VvmLog.e(TAG, "Visual voicemail not available for subscriber.");
252    }
253  }
254
255  private static void onSuccess(Context context, PhoneAccountHandle phoneAccountHandle) {
256    OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(context, phoneAccountHandle);
257    helper.handleEvent(
258        VoicemailStatus.edit(context, phoneAccountHandle),
259        OmtpEvents.CONFIG_REQUEST_STATUS_SUCCESS);
260    clearLegacyVoicemailNotification(context, phoneAccountHandle);
261    SyncTask.start(context, phoneAccountHandle, OmtpVvmSyncService.SYNC_FULL_SYNC);
262  }
263
264  /** Sends a broadcast to the dialer UI to clear legacy voicemail notifications if any. */
265  private static void clearLegacyVoicemailNotification(
266      Context context, PhoneAccountHandle phoneAccountHandle) {
267    Intent intent = new Intent(VoicemailClient.ACTION_SHOW_LEGACY_VOICEMAIL);
268    intent.setPackage(context.getPackageName());
269    intent.putExtra(TelephonyManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
270    // Setting voicemail message count to zero will clear the notification.
271    intent.putExtra(TelephonyManager.EXTRA_NOTIFICATION_COUNT, 0);
272    context.sendBroadcast(intent);
273  }
274
275  private static boolean hasSignal(Context context, PhoneAccountHandle phoneAccountHandle) {
276    TelephonyManager telephonyManager =
277        context
278            .getSystemService(TelephonyManager.class)
279            .createForPhoneAccountHandle(phoneAccountHandle);
280    return telephonyManager.getServiceState().getState() == ServiceState.STATE_IN_SERVICE;
281  }
282}
283