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