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