blob: 6bcd9ec2bec49d1c104dd59c7f7e8cdfcac90845 [file] [log] [blame]
/*
* Copyright (C) 2021 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.imsserviceentitlement;
import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__CANCELED;
import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__DISABLED;
import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__FAILED;
import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__INCOMPATIBLE;
import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__SUCCESSFUL;
import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__TIMEOUT;
import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNEXPECTED_RESULT;
import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT;
import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__PURPOSE__ACTIVATION;
import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__PURPOSE__UPDATE;
import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__SERVICE_TYPE__VOWIFI;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.CountDownTimer;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import com.android.imsserviceentitlement.entitlement.EntitlementResult;
import com.android.imsserviceentitlement.ts43.Ts43VowifiStatus;
import com.android.imsserviceentitlement.utils.ImsUtils;
import com.android.imsserviceentitlement.utils.MetricsLogger;
import com.android.imsserviceentitlement.utils.TelephonyUtils;
import com.google.common.annotations.VisibleForTesting;
import java.time.Duration;
/**
* The driver for WFC activation workflow: go/vowifi-entitlement-status-analysis.
*
* <p>One {@link WfcActivationActivity} owns one and only one controller instance.
*/
public class WfcActivationController {
private static final String TAG = "IMSSE-WfcActivationController";
// Entitlement status update retry
private static final int ENTITLEMENT_STATUS_UPDATE_RETRY_MAX = 6;
private static final long ENTITLEMENT_STATUS_UPDATE_RETRY_INTERVAL_MS =
Duration.ofSeconds(5).toMillis();
// Dependencies
private final WfcActivationUi mActivationUi;
private final TelephonyUtils mTelephonyUtils;
private final ImsEntitlementApi mImsEntitlementApi;
private final ImsUtils mImsUtils;
private final Intent mStartIntent;
private final MetricsLogger mMetricsLogger;
// States
private int mEvaluateTimes = 0;
private int mAppResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT;
@MainThread
public WfcActivationController(
Context context,
WfcActivationUi wfcActivationUi,
ImsEntitlementApi imsEntitlementApi,
Intent intent) {
this.mStartIntent = intent;
this.mActivationUi = wfcActivationUi;
this.mImsEntitlementApi = imsEntitlementApi;
this.mTelephonyUtils = new TelephonyUtils(context, getSubId());
this.mImsUtils = ImsUtils.getInstance(context, getSubId());
this.mMetricsLogger = new MetricsLogger(mTelephonyUtils);
}
@VisibleForTesting
WfcActivationController(
Context context,
WfcActivationUi wfcActivationUi,
ImsEntitlementApi imsEntitlementApi,
Intent intent,
ImsUtils imsUtils,
MetricsLogger metricsLogger) {
this.mStartIntent = intent;
this.mActivationUi = wfcActivationUi;
this.mImsEntitlementApi = imsEntitlementApi;
this.mTelephonyUtils = new TelephonyUtils(context, getSubId());
this.mImsUtils = imsUtils;
this.mMetricsLogger = metricsLogger;
}
/** Indicates the controller to start WFC activation or emergency address update flow. */
@MainThread
public void startFlow() {
showGeneralWaitingUi();
evaluateEntitlementStatus();
if (isActivationFlow()) {
mMetricsLogger.start(IMS_SERVICE_ENTITLEMENT_UPDATED__PURPOSE__ACTIVATION);
} else {
mMetricsLogger.start(IMS_SERVICE_ENTITLEMENT_UPDATED__PURPOSE__UPDATE);
}
}
/** Evaluates entitlement status for activation or update. */
@MainThread
public void evaluateEntitlementStatus() {
if (!mTelephonyUtils.isNetworkConnected()) {
handleInitialEntitlementStatus(null);
return;
}
EntitlementUtils.entitlementCheck(
mImsEntitlementApi, result -> handleInitialEntitlementStatus(result));
}
/**
* Indicates the controller to re-evaluate WFC entitlement status after activation flow finished
* successfully (ie. not canceled) by user.
*/
@MainThread
public void finishFlow() {
showGeneralWaitingUi();
reevaluateEntitlementStatus();
}
/** Re-evaluate entitlement status after updating. */
@MainThread
public void reevaluateEntitlementStatus() {
EntitlementUtils.entitlementCheck(
mImsEntitlementApi, result -> handleReevaluationEntitlementStatus(result));
}
/** The interface for handling the entitlement check result. */
public interface EntitlementResultCallback {
void onEntitlementResult(EntitlementResult result);
}
/** Indicates the controller to finish on-going tasks and get ready to be destroyed. */
@MainThread
public void finish() {
EntitlementUtils.cancelEntitlementCheck();
// If no result set, it must be cancelled by user pressing back button.
if (mAppResult == IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT) {
mAppResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__CANCELED;
}
mMetricsLogger.write(IMS_SERVICE_ENTITLEMENT_UPDATED__SERVICE_TYPE__VOWIFI, mAppResult);
}
/**
* Returns {@code true} if the app is launched for WFC activation; {@code false} for emergency
* address update.
*/
private boolean isActivationFlow() {
return ActivityConstants.isActivationFlow(mStartIntent);
}
private int getSubId() {
return ActivityConstants.getSubId(mStartIntent);
}
/** Returns UI title string resource ID based on {@link #isActivationFlow()}. */
@StringRes
private int getUiTitle() {
int intention = ActivityConstants.getLaunchIntention(mStartIntent);
if (intention == ActivityConstants.LAUNCH_APP_ACTIVATE) {
return R.string.activate_title;
}
if (intention == ActivityConstants.LAUNCH_APP_SHOW_TC) {
return R.string.tos_title;
}
// LAUNCH_APP_UPDATE or otherwise
return R.string.e911_title;
}
/** Returns general error string resource ID based on {@link #isActivationFlow()}. */
@StringRes
private int getGeneralErrorText() {
int intention = ActivityConstants.getLaunchIntention(mStartIntent);
if (intention == ActivityConstants.LAUNCH_APP_ACTIVATE) {
return R.string.wfc_activation_error;
} else if (intention == ActivityConstants.LAUNCH_APP_SHOW_TC) {
return R.string.show_terms_and_condition_error;
}
// LAUNCH_APP_UPDATE or otherwise
return R.string.address_update_error;
}
private void showErrorUi(@StringRes int errorMessage) {
mActivationUi.showActivationUi(
getUiTitle(), errorMessage, false, R.string.ok, WfcActivationUi.RESULT_FAILURE, 0);
}
private void showGeneralErrorUi() {
showErrorUi(getGeneralErrorText());
}
private void showGeneralWaitingUi() {
mActivationUi.showActivationUi(getUiTitle(), R.string.progress_text, true, 0, 0, 0);
}
@MainThread
private void handleInitialEntitlementStatus(@Nullable EntitlementResult result) {
Log.d(TAG, "Initial entitlement result: " + result);
if (result == null) {
showGeneralErrorUi();
finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__FAILED);
return;
}
if (isActivationFlow()) {
handleEntitlementStatusForActivation(result);
} else {
handleEntitlementStatusForUpdating(result);
}
}
@MainThread
private void handleEntitlementStatusForActivation(EntitlementResult result) {
Ts43VowifiStatus vowifiStatus = result.getVowifiStatus();
if (vowifiStatus.vowifiEntitled()) {
finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__SUCCESSFUL);
mActivationUi.setResultAndFinish(Activity.RESULT_OK);
} else {
if (vowifiStatus.serverDataMissing()) {
if (!TextUtils.isEmpty(result.getTermsAndConditionsWebUrl())) {
mActivationUi.showWebview(
result.getTermsAndConditionsWebUrl(), /* postData= */ null);
} else {
mActivationUi.showWebview(
result.getEmergencyAddressWebUrl(),
result.getEmergencyAddressWebData());
}
} else if (vowifiStatus.incompatible()) {
finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__INCOMPATIBLE);
showErrorUi(R.string.failure_contact_carrier);
} else {
Log.e(TAG, "Unexpected status. Show error UI.");
finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNEXPECTED_RESULT);
showGeneralErrorUi();
}
}
}
@MainThread
private void handleEntitlementStatusForUpdating(EntitlementResult result) {
Ts43VowifiStatus vowifiStatus = result.getVowifiStatus();
if (vowifiStatus.vowifiEntitled()) {
int launchIntention = ActivityConstants.getLaunchIntention(mStartIntent);
if (launchIntention == ActivityConstants.LAUNCH_APP_SHOW_TC) {
mActivationUi.showWebview(
result.getTermsAndConditionsWebUrl(), /* postData= */ null);
} else {
mActivationUi.showWebview(
result.getEmergencyAddressWebUrl(), result.getEmergencyAddressWebData());
}
} else {
if (vowifiStatus.incompatible()) {
showErrorUi(R.string.failure_contact_carrier);
mImsUtils.turnOffWfc(
() -> finishStatsLog(
IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__INCOMPATIBLE)
);
} else {
Log.e(TAG, "Unexpected status. Show error UI.");
finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNEXPECTED_RESULT);
showGeneralErrorUi();
}
}
}
@MainThread
private void handleReevaluationEntitlementStatus(@Nullable EntitlementResult result) {
Log.d(TAG, "Reevaluation entitlement result: " + result);
if (result == null) { // Network issue
showGeneralErrorUi();
finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__FAILED);
return;
}
if (isActivationFlow()) {
handleEntitlementStatusAfterActivation(result);
} else {
handleEntitlementStatusAfterUpdating(result);
}
}
@MainThread
private void handleEntitlementStatusAfterActivation(EntitlementResult result) {
Ts43VowifiStatus vowifiStatus = result.getVowifiStatus();
if (vowifiStatus.vowifiEntitled()) {
mActivationUi.setResultAndFinish(Activity.RESULT_OK);
finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__SUCCESSFUL);
} else {
if (vowifiStatus.serverDataMissing()) {
// Check again after 5s, max retry 6 times
if (mEvaluateTimes < ENTITLEMENT_STATUS_UPDATE_RETRY_MAX) {
mEvaluateTimes += 1;
postDelay(
getEntitlementStatusUpdateRetryIntervalMs(),
this::reevaluateEntitlementStatus);
} else {
mEvaluateTimes = 0;
showGeneralErrorUi();
finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__TIMEOUT);
}
} else {
// These should never happen, but nothing else we can do. Show general error.
Log.e(TAG, "Unexpected status. Show error UI.");
showGeneralErrorUi();
finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNEXPECTED_RESULT);
}
}
}
private long getEntitlementStatusUpdateRetryIntervalMs() {
return ENTITLEMENT_STATUS_UPDATE_RETRY_INTERVAL_MS;
}
@MainThread
private void handleEntitlementStatusAfterUpdating(EntitlementResult result) {
Ts43VowifiStatus vowifiStatus = result.getVowifiStatus();
if (vowifiStatus.vowifiEntitled()) {
mActivationUi.setResultAndFinish(Activity.RESULT_OK);
finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__SUCCESSFUL);
} else if (vowifiStatus.serverDataMissing()) {
// Some carrier allows de-activating in updating flow.
mImsUtils.turnOffWfc(
() -> {
finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__DISABLED);
mActivationUi.setResultAndFinish(Activity.RESULT_OK);
});
} else {
Log.e(TAG, "Unexpected status. Show error UI.");
showGeneralErrorUi();
finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNEXPECTED_RESULT);
}
}
/** Runs {@code action} on caller's thread after {@code delayMillis} ms. */
private static void postDelay(long delayMillis, Runnable action) {
new CountDownTimer(delayMillis, delayMillis + 100) {
// Use a countDownInterval bigger than millisInFuture so onTick never fires.
@Override
public void onTick(long millisUntilFinished) {
// Do nothing
}
@Override
public void onFinish() {
action.run();
}
}.start();
}
private void finishStatsLog(int result) {
mAppResult = result;
}
}