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