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