306 lines
12 KiB
Java
306 lines
12 KiB
Java
/*
|
|
* Copyright (C) 2016 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License
|
|
*/
|
|
|
|
package com.android.voicemail.impl;
|
|
|
|
import android.annotation.TargetApi;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.os.Build.VERSION_CODES;
|
|
import android.os.Bundle;
|
|
import android.provider.Settings;
|
|
import android.support.annotation.Nullable;
|
|
import android.support.annotation.VisibleForTesting;
|
|
import android.support.annotation.WorkerThread;
|
|
import android.telecom.PhoneAccountHandle;
|
|
import android.telephony.ServiceState;
|
|
import android.telephony.TelephonyManager;
|
|
import com.android.dialer.logging.DialerImpression;
|
|
import com.android.dialer.proguard.UsedByReflection;
|
|
import com.android.voicemail.VoicemailClient;
|
|
import com.android.voicemail.impl.protocol.VisualVoicemailProtocol;
|
|
import com.android.voicemail.impl.scheduling.BaseTask;
|
|
import com.android.voicemail.impl.scheduling.RetryPolicy;
|
|
import com.android.voicemail.impl.settings.VisualVoicemailSettingsUtil;
|
|
import com.android.voicemail.impl.sms.StatusMessage;
|
|
import com.android.voicemail.impl.sms.StatusSmsFetcher;
|
|
import com.android.voicemail.impl.sync.SyncTask;
|
|
import com.android.voicemail.impl.sync.VvmAccountManager;
|
|
import com.android.voicemail.impl.utils.LoggerUtils;
|
|
import java.io.IOException;
|
|
import java.util.concurrent.CancellationException;
|
|
import java.util.concurrent.ExecutionException;
|
|
import java.util.concurrent.TimeoutException;
|
|
|
|
/**
|
|
* Task to activate the visual voicemail service. A request to activate VVM will be sent to the
|
|
* carrier, which will respond with a STATUS SMS. The credentials will be updated from the SMS. If
|
|
* the user is not provisioned provisioning will be attempted. Activation happens when the phone
|
|
* boots, the SIM is inserted, signal returned when VVM is not activated yet, and when the carrier
|
|
* spontaneously sent a STATUS SMS.
|
|
*/
|
|
@TargetApi(VERSION_CODES.O)
|
|
@UsedByReflection(value = "Tasks.java")
|
|
public class ActivationTask extends BaseTask {
|
|
|
|
private static final String TAG = "VvmActivationTask";
|
|
|
|
private static final int RETRY_TIMES = 4;
|
|
private static final int RETRY_INTERVAL_MILLIS = 5_000;
|
|
|
|
@VisibleForTesting static final String EXTRA_MESSAGE_DATA_BUNDLE = "extra_message_data_bundle";
|
|
|
|
private final RetryPolicy retryPolicy;
|
|
|
|
@Nullable private OmtpVvmCarrierConfigHelper configForTest;
|
|
|
|
private Bundle messageData;
|
|
|
|
public ActivationTask() {
|
|
super(TASK_ACTIVATION);
|
|
retryPolicy = new RetryPolicy(RETRY_TIMES, RETRY_INTERVAL_MILLIS);
|
|
addPolicy(retryPolicy);
|
|
}
|
|
|
|
/** Has the user gone through the setup wizard yet. */
|
|
private static boolean isDeviceProvisioned(Context context) {
|
|
return Settings.Global.getInt(
|
|
context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0)
|
|
== 1;
|
|
}
|
|
|
|
/**
|
|
* @param messageData The optional bundle from {@link android.provider.VoicemailContract#
|
|
* EXTRA_VOICEMAIL_SMS_FIELDS}, if the task is initiated by a status SMS. If null the task
|
|
* will request a status SMS itself.
|
|
*/
|
|
public static void start(
|
|
Context context, PhoneAccountHandle phoneAccountHandle, @Nullable Bundle messageData) {
|
|
if (!isDeviceProvisioned(context)) {
|
|
VvmLog.i(TAG, "Activation requested while device is not provisioned, postponing");
|
|
// Activation might need information such as system language to be set, so wait until
|
|
// the setup wizard is finished. The data bundle from the SMS will be re-requested upon
|
|
// activation.
|
|
DeviceProvisionedJobService.activateAfterProvisioned(context, phoneAccountHandle);
|
|
return;
|
|
}
|
|
|
|
Intent intent = BaseTask.createIntent(context, ActivationTask.class, phoneAccountHandle);
|
|
if (messageData != null) {
|
|
intent.putExtra(EXTRA_MESSAGE_DATA_BUNDLE, messageData);
|
|
}
|
|
context.sendBroadcast(intent);
|
|
}
|
|
|
|
@Override
|
|
public void onCreate(Context context, Bundle extras) {
|
|
super.onCreate(context, extras);
|
|
messageData = extras.getParcelable(EXTRA_MESSAGE_DATA_BUNDLE);
|
|
}
|
|
|
|
@Override
|
|
public Intent createRestartIntent() {
|
|
LoggerUtils.logImpressionOnMainThread(
|
|
getContext(), DialerImpression.Type.VVM_AUTO_RETRY_ACTIVATION);
|
|
Intent intent = super.createRestartIntent();
|
|
// mMessageData is discarded, request a fresh STATUS SMS for retries.
|
|
return intent;
|
|
}
|
|
|
|
@Override
|
|
@WorkerThread
|
|
public void onExecuteInBackgroundThread() {
|
|
Assert.isNotMainThread();
|
|
LoggerUtils.logImpressionOnMainThread(
|
|
getContext(), DialerImpression.Type.VVM_ACTIVATION_STARTED);
|
|
PhoneAccountHandle phoneAccountHandle = getPhoneAccountHandle();
|
|
if (phoneAccountHandle == null) {
|
|
// This should never happen
|
|
VvmLog.e(TAG, "null PhoneAccountHandle");
|
|
return;
|
|
}
|
|
|
|
PreOMigrationHandler.migrate(getContext(), phoneAccountHandle);
|
|
|
|
OmtpVvmCarrierConfigHelper helper;
|
|
if (configForTest != null) {
|
|
helper = configForTest;
|
|
} else {
|
|
helper = new OmtpVvmCarrierConfigHelper(getContext(), phoneAccountHandle);
|
|
}
|
|
if (!helper.isValid()) {
|
|
VvmLog.i(TAG, "VVM not supported on phoneAccountHandle " + phoneAccountHandle);
|
|
VvmAccountManager.removeAccount(getContext(), phoneAccountHandle);
|
|
return;
|
|
}
|
|
|
|
if (!VisualVoicemailSettingsUtil.isEnabled(getContext(), phoneAccountHandle)) {
|
|
if (helper.isLegacyModeEnabled()) {
|
|
VvmLog.i(TAG, "Setting up filter for legacy mode");
|
|
helper.activateSmsFilter();
|
|
}
|
|
VvmLog.i(TAG, "VVM is disabled");
|
|
return;
|
|
}
|
|
|
|
// OmtpVvmCarrierConfigHelper can start the activation process; it will pass in a vvm
|
|
// content provider URI which we will use. On some occasions, setting that URI will
|
|
// fail, so we will perform a few attempts to ensure that the vvm content provider has
|
|
// a good chance of being started up.
|
|
if (!VoicemailStatus.edit(getContext(), phoneAccountHandle)
|
|
.setType(helper.getVvmType())
|
|
.apply()) {
|
|
VvmLog.e(TAG, "Failed to configure content provider - " + helper.getVvmType());
|
|
fail();
|
|
}
|
|
VvmLog.i(TAG, "VVM content provider configured - " + helper.getVvmType());
|
|
|
|
if (messageData == null
|
|
&& VvmAccountManager.isAccountActivated(getContext(), phoneAccountHandle)) {
|
|
VvmLog.i(TAG, "Account is already activated");
|
|
// The activated state might come from restored data, the filter still needs to be set up.
|
|
helper.activateSmsFilter();
|
|
onSuccess(getContext(), phoneAccountHandle, helper);
|
|
return;
|
|
}
|
|
helper.handleEvent(
|
|
VoicemailStatus.edit(getContext(), phoneAccountHandle), OmtpEvents.CONFIG_ACTIVATING);
|
|
|
|
if (!hasSignal(getContext(), phoneAccountHandle)) {
|
|
VvmLog.i(TAG, "Service lost during activation, aborting");
|
|
// Restore the "NO SIGNAL" state since it will be overwritten by the CONFIG_ACTIVATING
|
|
// event.
|
|
helper.handleEvent(
|
|
VoicemailStatus.edit(getContext(), phoneAccountHandle),
|
|
OmtpEvents.NOTIFICATION_SERVICE_LOST);
|
|
// Don't retry, a new activation will be started after the signal returned.
|
|
return;
|
|
}
|
|
|
|
helper.activateSmsFilter();
|
|
VoicemailStatus.Editor status = retryPolicy.getVoicemailStatusEditor();
|
|
|
|
VisualVoicemailProtocol protocol = helper.getProtocol();
|
|
|
|
Bundle data;
|
|
boolean isCarrierInitiated = messageData != null;
|
|
if (isCarrierInitiated) {
|
|
// The content of STATUS SMS is provided to launch this task, no need to request it
|
|
// again.
|
|
data = messageData;
|
|
} else {
|
|
try (StatusSmsFetcher fetcher = new StatusSmsFetcher(getContext(), phoneAccountHandle)) {
|
|
protocol.startActivation(helper, fetcher.getSentIntent());
|
|
// Both the fetcher and OmtpMessageReceiver will be triggered, but
|
|
// OmtpMessageReceiver will just route the SMS back to ActivationTask, which will be
|
|
// rejected because the task is still running.
|
|
data = fetcher.get();
|
|
} catch (TimeoutException e) {
|
|
// The carrier is expected to return an STATUS SMS within STATUS_SMS_TIMEOUT_MILLIS
|
|
// handleEvent() will do the logging.
|
|
helper.handleEvent(status, OmtpEvents.CONFIG_STATUS_SMS_TIME_OUT);
|
|
fail();
|
|
return;
|
|
} catch (CancellationException e) {
|
|
VvmLog.e(TAG, "Unable to send status request SMS");
|
|
fail();
|
|
return;
|
|
} catch (InterruptedException | ExecutionException | IOException e) {
|
|
VvmLog.e(TAG, "can't get future STATUS SMS", e);
|
|
fail();
|
|
return;
|
|
}
|
|
}
|
|
|
|
StatusMessage message = new StatusMessage(data);
|
|
VvmLog.d(
|
|
TAG,
|
|
"STATUS SMS received: st="
|
|
+ message.getProvisioningStatus()
|
|
+ ", rc="
|
|
+ message.getReturnCode());
|
|
if (message.getProvisioningStatus().equals(OmtpConstants.SUBSCRIBER_READY)) {
|
|
VvmLog.d(TAG, "subscriber ready, no activation required");
|
|
updateSource(getContext(), phoneAccountHandle, message, helper);
|
|
} else {
|
|
if (helper.supportsProvisioning()) {
|
|
VvmLog.i(TAG, "Subscriber not ready, start provisioning");
|
|
helper.startProvisioning(
|
|
this, phoneAccountHandle, status, message, data, isCarrierInitiated);
|
|
|
|
} else if (message.getProvisioningStatus().equals(OmtpConstants.SUBSCRIBER_NEW)) {
|
|
VvmLog.i(TAG, "Subscriber new but provisioning is not supported");
|
|
// Ignore the non-ready state and attempt to use the provided info as is.
|
|
// This is probably caused by not completing the new user tutorial.
|
|
updateSource(getContext(), phoneAccountHandle, message, helper);
|
|
} else {
|
|
VvmLog.i(TAG, "Subscriber not ready but provisioning is not supported");
|
|
helper.handleEvent(status, OmtpEvents.CONFIG_SERVICE_NOT_AVAILABLE);
|
|
}
|
|
}
|
|
LoggerUtils.logImpressionOnMainThread(
|
|
getContext(), DialerImpression.Type.VVM_ACTIVATION_COMPLETED);
|
|
}
|
|
|
|
private static void updateSource(
|
|
Context context,
|
|
PhoneAccountHandle phone,
|
|
StatusMessage message,
|
|
OmtpVvmCarrierConfigHelper config) {
|
|
|
|
if (OmtpConstants.SUCCESS.equals(message.getReturnCode())) {
|
|
// Save the IMAP credentials in preferences so they are persistent and can be retrieved.
|
|
VvmAccountManager.addAccount(context, phone, message);
|
|
onSuccess(context, phone, config);
|
|
} else {
|
|
VvmLog.e(TAG, "Visual voicemail not available for subscriber.");
|
|
}
|
|
}
|
|
|
|
private static void onSuccess(
|
|
Context context, PhoneAccountHandle phoneAccountHandle, OmtpVvmCarrierConfigHelper config) {
|
|
config.handleEvent(
|
|
VoicemailStatus.edit(context, phoneAccountHandle),
|
|
OmtpEvents.CONFIG_REQUEST_STATUS_SUCCESS);
|
|
clearLegacyVoicemailNotification(context, phoneAccountHandle);
|
|
SyncTask.start(context, phoneAccountHandle);
|
|
}
|
|
|
|
/** Sends a broadcast to the dialer UI to clear legacy voicemail notifications if any. */
|
|
private static void clearLegacyVoicemailNotification(
|
|
Context context, PhoneAccountHandle phoneAccountHandle) {
|
|
Intent intent = new Intent(VoicemailClient.ACTION_SHOW_LEGACY_VOICEMAIL);
|
|
intent.setPackage(context.getPackageName());
|
|
intent.putExtra(TelephonyManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
|
|
// Setting voicemail message count to zero will clear the notification.
|
|
intent.putExtra(TelephonyManager.EXTRA_NOTIFICATION_COUNT, 0);
|
|
context.sendBroadcast(intent);
|
|
}
|
|
|
|
private static boolean hasSignal(Context context, PhoneAccountHandle phoneAccountHandle) {
|
|
TelephonyManager telephonyManager =
|
|
context
|
|
.getSystemService(TelephonyManager.class)
|
|
.createForPhoneAccountHandle(phoneAccountHandle);
|
|
return telephonyManager.getServiceState().getState() == ServiceState.STATE_IN_SERVICE;
|
|
}
|
|
|
|
@VisibleForTesting
|
|
void setConfigForTest(OmtpVvmCarrierConfigHelper config) {
|
|
configForTest = config;
|
|
}
|
|
}
|