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