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.phone.vvm.omtp; 18 19import android.annotation.Nullable; 20import android.annotation.WorkerThread; 21import android.content.Context; 22import android.content.Intent; 23import android.database.ContentObserver; 24import android.os.Bundle; 25import android.provider.Settings; 26import android.provider.Settings.Global; 27import android.telecom.PhoneAccountHandle; 28import android.telephony.ServiceState; 29import android.telephony.TelephonyManager; 30 31import com.android.phone.Assert; 32import com.android.phone.PhoneGlobals; 33import com.android.phone.VoicemailStatus; 34import com.android.phone.vvm.omtp.protocol.VisualVoicemailProtocol; 35import com.android.phone.vvm.omtp.scheduling.BaseTask; 36import com.android.phone.vvm.omtp.scheduling.RetryPolicy; 37import com.android.phone.vvm.omtp.sms.StatusMessage; 38import com.android.phone.vvm.omtp.sms.StatusSmsFetcher; 39import com.android.phone.vvm.omtp.sync.OmtpVvmSourceManager; 40import com.android.phone.vvm.omtp.sync.OmtpVvmSyncService; 41import com.android.phone.vvm.omtp.sync.SyncTask; 42import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter; 43 44import java.io.IOException; 45import java.util.HashSet; 46import java.util.Set; 47import java.util.concurrent.CancellationException; 48import java.util.concurrent.ExecutionException; 49import java.util.concurrent.TimeoutException; 50 51/** 52 * Task to activate the visual voicemail service. A request to activate VVM will be sent to the 53 * carrier, which will respond with a STATUS SMS. The credentials will be updated from the SMS. If 54 * the user is not provisioned provisioning will be attempted. Activation happens when the phone 55 * boots, the SIM is inserted, signal returned when VVM is not activated yet, and when the carrier 56 * spontaneously sent a STATUS SMS. 57 */ 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 68 private static DeviceProvisionedObserver sDeviceProvisionedObserver; 69 70 private final RetryPolicy mRetryPolicy; 71 72 private Bundle mMessageData; 73 74 public ActivationTask() { 75 super(TASK_ACTIVATION); 76 mRetryPolicy = new RetryPolicy(RETRY_TIMES, RETRY_INTERVAL_MILLIS); 77 addPolicy(mRetryPolicy); 78 } 79 80 /** 81 * Has the user gone through the setup wizard yet. 82 */ 83 private static boolean isDeviceProvisioned(Context context) { 84 return Settings.Global.getInt( 85 context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) == 1; 86 } 87 88 /** 89 * @param messageData The optional bundle from {@link android.provider.VoicemailContract# 90 * EXTRA_VOICEMAIL_SMS_FIELDS}, if the task is initiated by a status SMS. If null the task will 91 * request a status SMS itself. 92 */ 93 public static void start(Context context, int subId, @Nullable Bundle messageData) { 94 if (!isDeviceProvisioned(context)) { 95 VvmLog.i(TAG, "Activation requested while device is not provisioned, postponing"); 96 // Activation might need information such as system language to be set, so wait until 97 // the setup wizard is finished. The data bundle from the SMS will be re-requested upon 98 // activation. 99 queueActivationAfterProvisioned(context, subId); 100 return; 101 } 102 103 Intent intent = BaseTask.createIntent(context, ActivationTask.class, subId); 104 if (messageData != null) { 105 intent.putExtra(EXTRA_MESSAGE_DATA_BUNDLE, messageData); 106 } 107 context.startService(intent); 108 } 109 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 Intent intent = super.createRestartIntent(); 118 // mMessageData is discarded, request a fresh STATUS SMS for retries. 119 return intent; 120 } 121 122 @Override 123 @WorkerThread 124 public void onExecuteInBackgroundThread() { 125 Assert.isNotMainThread(); 126 int subId = getSubId(); 127 128 PhoneAccountHandle phoneAccountHandle = PhoneAccountHandleConverter.fromSubId(subId); 129 if (phoneAccountHandle == null) { 130 // This should never happen 131 VvmLog.e(TAG, "null phone account for subId " + subId); 132 return; 133 } 134 135 OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(getContext(), subId); 136 if (!helper.isValid()) { 137 VvmLog.i(TAG, "VVM not supported on subId " + subId); 138 VoicemailStatus.disable(getContext(), phoneAccountHandle); 139 return; 140 } 141 142 // OmtpVvmCarrierConfigHelper can start the activation process; it will pass in a vvm 143 // content provider URI which we will use. On some occasions, setting that URI will 144 // fail, so we will perform a few attempts to ensure that the vvm content provider has 145 // a good chance of being started up. 146 if (!VoicemailStatus.edit(getContext(), phoneAccountHandle) 147 .setType(helper.getVvmType()) 148 .apply()) { 149 VvmLog.e(TAG, "Failed to configure content provider - " + helper.getVvmType()); 150 fail(); 151 } 152 VvmLog.i(TAG, "VVM content provider configured - " + helper.getVvmType()); 153 154 if (!OmtpVvmSourceManager.getInstance(getContext()) 155 .isVvmSourceRegistered(phoneAccountHandle)) { 156 // This account has not been activated before during the lifetime of this boot. 157 VisualVoicemailPreferences preferences = new VisualVoicemailPreferences(getContext(), 158 phoneAccountHandle); 159 if (preferences.getString(OmtpConstants.SERVER_ADDRESS, null) == null) { 160 // Only show the "activating" message if activation has not been completed before. 161 // Subsequent activations are more of a status check and usually does not 162 // concern the user. 163 helper.handleEvent(VoicemailStatus.edit(getContext(), phoneAccountHandle), 164 OmtpEvents.CONFIG_ACTIVATING); 165 } else { 166 // The account has been activated on this device before. Pretend it is already 167 // activated. If there are any activation error it will overwrite this status. 168 helper.handleEvent(VoicemailStatus.edit(getContext(), phoneAccountHandle), 169 OmtpEvents.CONFIG_ACTIVATING_SUBSEQUENT); 170 } 171 172 } 173 if (!hasSignal(getContext(), subId)) { 174 VvmLog.i(TAG, "Service lost during activation, aborting"); 175 // Restore the "NO SIGNAL" state since it will be overwritten by the CONFIG_ACTIVATING 176 // event. 177 helper.handleEvent(VoicemailStatus.edit(getContext(), phoneAccountHandle), 178 OmtpEvents.NOTIFICATION_SERVICE_LOST); 179 // Don't retry, a new activation will be started after the signal returned. 180 return; 181 } 182 183 helper.activateSmsFilter(); 184 VoicemailStatus.Editor status = mRetryPolicy.getVoicemailStatusEditor(); 185 186 VisualVoicemailProtocol protocol = helper.getProtocol(); 187 188 Bundle data; 189 if (mMessageData != null) { 190 // The content of STATUS SMS is provided to launch this task, no need to request it 191 // again. 192 data = mMessageData; 193 } else { 194 try (StatusSmsFetcher fetcher = new StatusSmsFetcher(getContext(), subId)) { 195 protocol.startActivation(helper, fetcher.getSentIntent()); 196 // Both the fetcher and OmtpMessageReceiver will be triggered, but 197 // OmtpMessageReceiver will just route the SMS back to ActivationTask, which will be 198 // rejected because the task is still running. 199 data = fetcher.get(); 200 } catch (TimeoutException e) { 201 // The carrier is expected to return an STATUS SMS within STATUS_SMS_TIMEOUT_MILLIS 202 // handleEvent() will do the logging. 203 helper.handleEvent(status, OmtpEvents.CONFIG_STATUS_SMS_TIME_OUT); 204 fail(); 205 return; 206 } catch (CancellationException e) { 207 VvmLog.e(TAG, "Unable to send status request SMS"); 208 fail(); 209 return; 210 } catch (InterruptedException | ExecutionException | IOException e) { 211 VvmLog.e(TAG, "can't get future STATUS SMS", e); 212 fail(); 213 return; 214 } 215 } 216 217 StatusMessage message = new StatusMessage(data); 218 VvmLog.d(TAG, "STATUS SMS received: st=" + message.getProvisioningStatus() 219 + ", rc=" + message.getReturnCode()); 220 221 if (message.getProvisioningStatus().equals(OmtpConstants.SUBSCRIBER_READY)) { 222 VvmLog.d(TAG, "subscriber ready, no activation required"); 223 updateSource(getContext(), phoneAccountHandle, getSubId(), status, 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, getSubId(), status, 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 PhoneGlobals.getInstance().setShouldCheckVisualVoicemailConfigurationForMwi(subId, false); 238 } 239 } 240 } 241 242 public static void updateSource(Context context, PhoneAccountHandle phone, int subId, 243 VoicemailStatus.Editor status, StatusMessage message) { 244 OmtpVvmSourceManager vvmSourceManager = 245 OmtpVvmSourceManager.getInstance(context); 246 247 if (OmtpConstants.SUCCESS.equals(message.getReturnCode())) { 248 OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(context, subId); 249 helper.handleEvent(status, OmtpEvents.CONFIG_REQUEST_STATUS_SUCCESS); 250 251 // Save the IMAP credentials in preferences so they are persistent and can be retrieved. 252 VisualVoicemailPreferences prefs = new VisualVoicemailPreferences(context, phone); 253 message.putStatus(prefs.edit()).apply(); 254 255 // Add the source to indicate that it is active. 256 vvmSourceManager.addSource(phone); 257 258 SyncTask.start(context, phone, OmtpVvmSyncService.SYNC_FULL_SYNC); 259 // Remove the message waiting indicator, which is a sticky notification for traditional 260 // voicemails. 261 PhoneGlobals.getInstance() 262 .setShouldCheckVisualVoicemailConfigurationForMwi(subId, true); 263 PhoneGlobals.getInstance().clearMwiIndicator(subId); 264 } else { 265 VvmLog.e(TAG, "Visual voicemail not available for subscriber."); 266 } 267 } 268 269 private static boolean hasSignal(Context context, int subId) { 270 return context.getSystemService(TelephonyManager.class) 271 .getServiceStateForSubscriber(subId).getState() == ServiceState.STATE_IN_SERVICE; 272 } 273 274 private static void queueActivationAfterProvisioned(Context context, int subId) { 275 if (sDeviceProvisionedObserver == null) { 276 sDeviceProvisionedObserver = new DeviceProvisionedObserver(context); 277 context.getContentResolver() 278 .registerContentObserver(Settings.Global.getUriFor(Global.DEVICE_PROVISIONED), 279 false, sDeviceProvisionedObserver); 280 } 281 sDeviceProvisionedObserver.addSubId(subId); 282 } 283 284 private static class DeviceProvisionedObserver extends ContentObserver { 285 286 private final Context mContext; 287 private final Set<Integer> mSubIds = new HashSet<>(); 288 289 private DeviceProvisionedObserver(Context context) { 290 super(null); 291 mContext = context; 292 } 293 294 public void addSubId(int subId) { 295 mSubIds.add(subId); 296 } 297 298 @Override 299 public void onChange(boolean selfChange) { 300 if (isDeviceProvisioned(mContext)) { 301 VvmLog.i(TAG, "device provisioned, resuming activation"); 302 for (int subId : mSubIds) { 303 start(mContext, subId, null); 304 } 305 mContext.getContentResolver().unregisterContentObserver(sDeviceProvisionedObserver); 306 sDeviceProvisionedObserver = null; 307 } 308 } 309 } 310} 311