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