• 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.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