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.provider.VoicemailContract.Status; 28 import android.telecom.PhoneAccountHandle; 29 import android.telephony.ServiceState; 30 import android.telephony.TelephonyManager; 31 import com.android.phone.Assert; 32 import com.android.phone.PhoneGlobals; 33 import com.android.phone.VoicemailStatus; 34 import com.android.phone.vvm.omtp.protocol.VisualVoicemailProtocol; 35 import com.android.phone.vvm.omtp.scheduling.BaseTask; 36 import com.android.phone.vvm.omtp.scheduling.RetryPolicy; 37 import com.android.phone.vvm.omtp.sms.StatusMessage; 38 import com.android.phone.vvm.omtp.sms.StatusSmsFetcher; 39 import com.android.phone.vvm.omtp.sync.OmtpVvmSourceManager; 40 import com.android.phone.vvm.omtp.sync.OmtpVvmSyncService; 41 import com.android.phone.vvm.omtp.sync.SyncTask; 42 import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter; 43 import java.io.IOException; 44 import java.util.HashSet; 45 import java.util.Set; 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 mData; 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 start(Context context, int subId, @Nullable Bundle data)86 public static void start(Context context, int subId, @Nullable Bundle data) { 87 if (!isDeviceProvisioned(context)) { 88 VvmLog.i(TAG, "Activation requested while device is not provisioned, postponing"); 89 // Activation might need information such as system language to be set, so wait until 90 // the setup wizard is finished. The data bundle from the SMS will be re-requested upon 91 // activation. 92 queueActivationAfterProvisioned(context, subId); 93 return; 94 } 95 96 // Check for signal before activating. The event often happen while boot and the 97 // network is not connected yet. Launching activation will likely to cause the SMS 98 // sending to fail and waste unnecessary time waiting for time out. 99 if (context.getSystemService(TelephonyManager.class) 100 .getServiceStateForSubscriber(subId).getState() 101 != ServiceState.STATE_IN_SERVICE) { 102 VvmLog.i(TAG, "Activation requested while not in service, rejecting"); 103 } 104 105 Intent intent = BaseTask.createIntent(context, ActivationTask.class, subId); 106 if (data != null) { 107 intent.putExtra(EXTRA_MESSAGE_DATA_BUNDLE, data); 108 } 109 context.startService(intent); 110 } 111 onCreate(Context context, Intent intent, int flags, int startId)112 public void onCreate(Context context, Intent intent, int flags, int startId) { 113 super.onCreate(context, intent, flags, startId); 114 mData = intent.getParcelableExtra(EXTRA_MESSAGE_DATA_BUNDLE); 115 } 116 117 @Override createRestartIntent()118 public Intent createRestartIntent() { 119 Intent intent = super.createRestartIntent(); 120 // mData is discarded, request a fresh STATUS SMS for retries. 121 return intent; 122 } 123 124 @Override 125 @WorkerThread onExecuteInBackgroundThread()126 public void onExecuteInBackgroundThread() { 127 Assert.isNotMainThread(); 128 int subId = getSubId(); 129 130 PhoneAccountHandle phoneAccountHandle = PhoneAccountHandleConverter.fromSubId(subId); 131 if (phoneAccountHandle == null) { 132 // This should never happen 133 VvmLog.e(TAG, "null phone account for subId " + subId); 134 return; 135 } 136 137 OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(getContext(), subId); 138 if (!helper.isValid()) { 139 VvmLog.i(TAG, "VVM not supported on subId " + subId); 140 VoicemailStatus.disable(getContext(), phoneAccountHandle); 141 return; 142 } 143 if (!OmtpVvmSourceManager.getInstance(getContext()) 144 .isVvmSourceRegistered(phoneAccountHandle)) { 145 VisualVoicemailPreferences preferences = new VisualVoicemailPreferences(getContext(), 146 phoneAccountHandle); 147 if (preferences.getString(OmtpConstants.SERVER_ADDRESS, null) == null) { 148 // Only show the "activating" message if activation has not been completed before. 149 // Subsequent activations are more of a status check and usually does not 150 // concern the user. 151 helper.handleEvent(VoicemailStatus.edit(getContext(), phoneAccountHandle), 152 OmtpEvents.CONFIG_ACTIVATING); 153 } else { 154 // The account has been activated on this device before. Pretend it is already 155 // activated. If there are any activation error it will overwrite this status. 156 VoicemailStatus.edit(getContext(), phoneAccountHandle) 157 .setConfigurationState(Status.CONFIGURATION_STATE_OK) 158 .apply(); 159 } 160 161 } 162 163 helper.activateSmsFilter(); 164 VoicemailStatus.Editor status = mRetryPolicy.getVoicemailStatusEditor(); 165 166 VisualVoicemailProtocol protocol = helper.getProtocol(); 167 168 Bundle data; 169 if (mData != null) { 170 // The content of STATUS SMS is provided to launch this task, no need to request it 171 // again. 172 data = mData; 173 } else { 174 try (StatusSmsFetcher fetcher = new StatusSmsFetcher(getContext(), subId)) { 175 protocol.startActivation(helper); 176 // Both the fetcher and OmtpMessageReceiver will be triggered, but 177 // OmtpMessageReceiver will just route the SMS back to ActivationTask, which will be 178 // rejected because the task is still running. 179 data = fetcher.get(); 180 } catch (TimeoutException e) { 181 // The carrier is expected to return an STATUS SMS within STATUS_SMS_TIMEOUT_MILLIS 182 // handleEvent() will do the logging. 183 helper.handleEvent(status, OmtpEvents.CONFIG_STATUS_SMS_TIME_OUT); 184 fail(); 185 return; 186 } catch (InterruptedException | ExecutionException | IOException e) { 187 VvmLog.e(TAG, "can't get future STATUS SMS", e); 188 fail(); 189 return; 190 } 191 } 192 193 StatusMessage message = new StatusMessage(data); 194 VvmLog.d(TAG, "STATUS SMS received: st=" + message.getProvisioningStatus() 195 + ", rc=" + message.getReturnCode()); 196 197 if (message.getProvisioningStatus().equals(OmtpConstants.SUBSCRIBER_READY)) { 198 VvmLog.d(TAG, "subscriber ready, no activation required"); 199 updateSource(getContext(), phoneAccountHandle, getSubId(), status, message); 200 } else { 201 if (helper.supportsProvisioning()) { 202 VvmLog.i(TAG, "Subscriber not ready, start provisioning"); 203 helper.startProvisioning(this, phoneAccountHandle, status, message, data); 204 205 } else if (message.getProvisioningStatus().equals(OmtpConstants.SUBSCRIBER_NEW)) { 206 VvmLog.i(TAG, "Subscriber new but provisioning is not supported"); 207 // Ignore the non-ready state and attempt to use the provided info as is. 208 // This is probably caused by not completing the new user tutorial. 209 updateSource(getContext(), phoneAccountHandle, getSubId(), status, message); 210 } else { 211 VvmLog.i(TAG, "Subscriber not ready but provisioning is not supported"); 212 helper.handleEvent(status, OmtpEvents.CONFIG_SERVICE_NOT_AVAILABLE); 213 } 214 } 215 } 216 updateSource(Context context, PhoneAccountHandle phone, int subId, VoicemailStatus.Editor status, StatusMessage message)217 public static void updateSource(Context context, PhoneAccountHandle phone, int subId, 218 VoicemailStatus.Editor status, StatusMessage message) { 219 OmtpVvmSourceManager vvmSourceManager = 220 OmtpVvmSourceManager.getInstance(context); 221 222 if (OmtpConstants.SUCCESS.equals(message.getReturnCode())) { 223 OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(context, subId); 224 helper.handleEvent(status, OmtpEvents.CONFIG_REQUEST_STATUS_SUCCESS); 225 226 // Save the IMAP credentials in preferences so they are persistent and can be retrieved. 227 VisualVoicemailPreferences prefs = new VisualVoicemailPreferences(context, phone); 228 message.putStatus(prefs.edit()).apply(); 229 230 // Add the source to indicate that it is active. 231 vvmSourceManager.addSource(phone); 232 233 SyncTask.start(context, phone, OmtpVvmSyncService.SYNC_FULL_SYNC); 234 // Remove the message waiting indicator, which is a stick notification fo traditional 235 // voicemails. 236 PhoneGlobals.getInstance().clearMwiIndicator(subId); 237 } else { 238 VvmLog.e(TAG, "Visual voicemail not available for subscriber."); 239 } 240 } 241 queueActivationAfterProvisioned(Context context, int subId)242 private static void queueActivationAfterProvisioned(Context context, int subId) { 243 if (sDeviceProvisionedObserver == null) { 244 sDeviceProvisionedObserver = new DeviceProvisionedObserver(context); 245 context.getContentResolver() 246 .registerContentObserver(Settings.Global.getUriFor(Global.DEVICE_PROVISIONED), 247 false, sDeviceProvisionedObserver); 248 } 249 sDeviceProvisionedObserver.addSubId(subId); 250 } 251 252 private static class DeviceProvisionedObserver extends ContentObserver { 253 254 private final Context mContext; 255 private final Set<Integer> mSubIds = new HashSet<>(); 256 DeviceProvisionedObserver(Context context)257 private DeviceProvisionedObserver(Context context) { 258 super(null); 259 mContext = context; 260 } 261 addSubId(int subId)262 public void addSubId(int subId) { 263 mSubIds.add(subId); 264 } 265 266 @Override onChange(boolean selfChange)267 public void onChange(boolean selfChange) { 268 if (isDeviceProvisioned(mContext)) { 269 VvmLog.i(TAG, "device provisioned, resuming activation"); 270 for (int subId : mSubIds) { 271 start(mContext, subId, null); 272 } 273 mContext.getContentResolver().unregisterContentObserver(sDeviceProvisionedObserver); 274 sDeviceProvisionedObserver = null; 275 } 276 } 277 } 278 } 279