• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ActivationTask()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. */
isDeviceProvisioned(Context context)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    */
start( Context context, PhoneAccountHandle phoneAccountHandle, @Nullable Bundle messageData)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
onCreate(Context context, Bundle extras)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
createRestartIntent()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
onExecuteInBackgroundThread()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 
updateSource( Context context, PhoneAccountHandle phone, StatusMessage message)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 
onSuccess(Context context, PhoneAccountHandle phoneAccountHandle)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. */
clearLegacyVoicemailNotification( Context context, PhoneAccountHandle phoneAccountHandle)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 
hasSignal(Context context, PhoneAccountHandle phoneAccountHandle)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