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