Home | History | Annotate | Download | only in impl
      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 
     17 package com.android.voicemail.impl;
     18 
     19 import android.annotation.TargetApi;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.os.Build.VERSION_CODES;
     23 import android.os.Bundle;
     24 import android.provider.Settings;
     25 import android.support.annotation.Nullable;
     26 import android.support.annotation.WorkerThread;
     27 import android.telecom.PhoneAccountHandle;
     28 import android.telephony.ServiceState;
     29 import android.telephony.TelephonyManager;
     30 import com.android.dialer.logging.DialerImpression;
     31 import com.android.dialer.proguard.UsedByReflection;
     32 import com.android.voicemail.VoicemailClient;
     33 import com.android.voicemail.impl.protocol.VisualVoicemailProtocol;
     34 import com.android.voicemail.impl.scheduling.BaseTask;
     35 import com.android.voicemail.impl.scheduling.RetryPolicy;
     36 import com.android.voicemail.impl.settings.VisualVoicemailSettingsUtil;
     37 import com.android.voicemail.impl.sms.StatusMessage;
     38 import com.android.voicemail.impl.sms.StatusSmsFetcher;
     39 import com.android.voicemail.impl.sync.OmtpVvmSyncService;
     40 import com.android.voicemail.impl.sync.SyncTask;
     41 import com.android.voicemail.impl.sync.VvmAccountManager;
     42 import com.android.voicemail.impl.utils.LoggerUtils;
     43 import java.io.IOException;
     44 import java.util.concurrent.CancellationException;
     45 import java.util.concurrent.ExecutionException;
     46 import 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")
     57 public 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