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