1 /* 2 * Copyright (C) 2021 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.imsserviceentitlement; 18 19 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__CANCELED; 20 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__DISABLED; 21 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__FAILED; 22 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__INCOMPATIBLE; 23 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__SUCCESSFUL; 24 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__TIMEOUT; 25 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNEXPECTED_RESULT; 26 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT; 27 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__PURPOSE__ACTIVATION; 28 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__PURPOSE__UPDATE; 29 import static com.android.imsserviceentitlement.ImsServiceEntitlementStatsLog.IMS_SERVICE_ENTITLEMENT_UPDATED__SERVICE_TYPE__VOWIFI; 30 31 import android.app.Activity; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.os.CountDownTimer; 35 import android.text.TextUtils; 36 import android.util.Log; 37 38 import androidx.annotation.MainThread; 39 import androidx.annotation.Nullable; 40 import androidx.annotation.StringRes; 41 42 import com.android.imsserviceentitlement.entitlement.EntitlementResult; 43 import com.android.imsserviceentitlement.ts43.Ts43VowifiStatus; 44 import com.android.imsserviceentitlement.utils.ImsUtils; 45 import com.android.imsserviceentitlement.utils.MetricsLogger; 46 import com.android.imsserviceentitlement.utils.TelephonyUtils; 47 48 import com.google.common.annotations.VisibleForTesting; 49 50 import java.time.Duration; 51 52 /** 53 * The driver for WFC activation workflow: go/vowifi-entitlement-status-analysis. 54 * 55 * <p>One {@link WfcActivationActivity} owns one and only one controller instance. 56 */ 57 public class WfcActivationController { 58 private static final String TAG = "IMSSE-WfcActivationController"; 59 60 // Entitlement status update retry 61 private static final int ENTITLEMENT_STATUS_UPDATE_RETRY_MAX = 6; 62 private static final long ENTITLEMENT_STATUS_UPDATE_RETRY_INTERVAL_MS = 63 Duration.ofSeconds(5).toMillis(); 64 65 // Dependencies 66 private final WfcActivationUi mActivationUi; 67 private final TelephonyUtils mTelephonyUtils; 68 private final ImsEntitlementApi mImsEntitlementApi; 69 private final ImsUtils mImsUtils; 70 private final Intent mStartIntent; 71 private final MetricsLogger mMetricsLogger; 72 73 // States 74 private int mEvaluateTimes = 0; 75 private int mAppResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT; 76 77 @MainThread WfcActivationController( Context context, WfcActivationUi wfcActivationUi, ImsEntitlementApi imsEntitlementApi, Intent intent)78 public WfcActivationController( 79 Context context, 80 WfcActivationUi wfcActivationUi, 81 ImsEntitlementApi imsEntitlementApi, 82 Intent intent) { 83 this.mStartIntent = intent; 84 this.mActivationUi = wfcActivationUi; 85 this.mImsEntitlementApi = imsEntitlementApi; 86 this.mTelephonyUtils = new TelephonyUtils(context, getSubId()); 87 this.mImsUtils = ImsUtils.getInstance(context, getSubId()); 88 this.mMetricsLogger = new MetricsLogger(mTelephonyUtils); 89 } 90 91 @VisibleForTesting WfcActivationController( Context context, WfcActivationUi wfcActivationUi, ImsEntitlementApi imsEntitlementApi, Intent intent, ImsUtils imsUtils, MetricsLogger metricsLogger)92 WfcActivationController( 93 Context context, 94 WfcActivationUi wfcActivationUi, 95 ImsEntitlementApi imsEntitlementApi, 96 Intent intent, 97 ImsUtils imsUtils, 98 MetricsLogger metricsLogger) { 99 this.mStartIntent = intent; 100 this.mActivationUi = wfcActivationUi; 101 this.mImsEntitlementApi = imsEntitlementApi; 102 this.mTelephonyUtils = new TelephonyUtils(context, getSubId()); 103 this.mImsUtils = imsUtils; 104 this.mMetricsLogger = metricsLogger; 105 } 106 107 /** Indicates the controller to start WFC activation or emergency address update flow. */ 108 @MainThread startFlow()109 public void startFlow() { 110 showGeneralWaitingUi(); 111 evaluateEntitlementStatus(); 112 if (isActivationFlow()) { 113 mMetricsLogger.start(IMS_SERVICE_ENTITLEMENT_UPDATED__PURPOSE__ACTIVATION); 114 } else { 115 mMetricsLogger.start(IMS_SERVICE_ENTITLEMENT_UPDATED__PURPOSE__UPDATE); 116 } 117 } 118 119 /** Evaluates entitlement status for activation or update. */ 120 @MainThread evaluateEntitlementStatus()121 public void evaluateEntitlementStatus() { 122 if (!mTelephonyUtils.isNetworkConnected()) { 123 handleInitialEntitlementStatus(null); 124 return; 125 } 126 EntitlementUtils.entitlementCheck( 127 mImsEntitlementApi, result -> handleInitialEntitlementStatus(result)); 128 } 129 130 /** 131 * Indicates the controller to re-evaluate WFC entitlement status after activation flow finished 132 * successfully (ie. not canceled) by user. 133 */ 134 @MainThread finishFlow()135 public void finishFlow() { 136 showGeneralWaitingUi(); 137 reevaluateEntitlementStatus(); 138 } 139 140 /** Re-evaluate entitlement status after updating. */ 141 @MainThread reevaluateEntitlementStatus()142 public void reevaluateEntitlementStatus() { 143 EntitlementUtils.entitlementCheck( 144 mImsEntitlementApi, result -> handleReevaluationEntitlementStatus(result)); 145 } 146 147 /** The interface for handling the entitlement check result. */ 148 public interface EntitlementResultCallback { onEntitlementResult(EntitlementResult result)149 void onEntitlementResult(EntitlementResult result); 150 } 151 152 /** Indicates the controller to finish on-going tasks and get ready to be destroyed. */ 153 @MainThread finish()154 public void finish() { 155 EntitlementUtils.cancelEntitlementCheck(); 156 157 // If no result set, it must be cancelled by user pressing back button. 158 if (mAppResult == IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNKNOWN_RESULT) { 159 mAppResult = IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__CANCELED; 160 } 161 mMetricsLogger.write(IMS_SERVICE_ENTITLEMENT_UPDATED__SERVICE_TYPE__VOWIFI, mAppResult); 162 } 163 164 /** 165 * Returns {@code true} if the app is launched for WFC activation; {@code false} for emergency 166 * address update. 167 */ isActivationFlow()168 private boolean isActivationFlow() { 169 return ActivityConstants.isActivationFlow(mStartIntent); 170 } 171 getSubId()172 private int getSubId() { 173 return ActivityConstants.getSubId(mStartIntent); 174 } 175 176 /** Returns UI title string resource ID based on {@link #isActivationFlow()}. */ 177 @StringRes getUiTitle()178 private int getUiTitle() { 179 int intention = ActivityConstants.getLaunchIntention(mStartIntent); 180 if (intention == ActivityConstants.LAUNCH_APP_ACTIVATE) { 181 return R.string.activate_title; 182 } 183 if (intention == ActivityConstants.LAUNCH_APP_SHOW_TC) { 184 return R.string.tos_title; 185 } 186 // LAUNCH_APP_UPDATE or otherwise 187 return R.string.e911_title; 188 } 189 190 /** Returns general error string resource ID based on {@link #isActivationFlow()}. */ 191 @StringRes getGeneralErrorText()192 private int getGeneralErrorText() { 193 int intention = ActivityConstants.getLaunchIntention(mStartIntent); 194 if (intention == ActivityConstants.LAUNCH_APP_ACTIVATE) { 195 return R.string.wfc_activation_error; 196 } else if (intention == ActivityConstants.LAUNCH_APP_SHOW_TC) { 197 return R.string.show_terms_and_condition_error; 198 } 199 // LAUNCH_APP_UPDATE or otherwise 200 return R.string.address_update_error; 201 } 202 showErrorUi(@tringRes int errorMessage)203 private void showErrorUi(@StringRes int errorMessage) { 204 mActivationUi.showActivationUi( 205 getUiTitle(), errorMessage, false, R.string.ok, WfcActivationUi.RESULT_FAILURE, 0); 206 } 207 showGeneralErrorUi()208 private void showGeneralErrorUi() { 209 showErrorUi(getGeneralErrorText()); 210 } 211 showGeneralWaitingUi()212 private void showGeneralWaitingUi() { 213 mActivationUi.showActivationUi(getUiTitle(), R.string.progress_text, true, 0, 0, 0); 214 } 215 216 @MainThread handleInitialEntitlementStatus(@ullable EntitlementResult result)217 private void handleInitialEntitlementStatus(@Nullable EntitlementResult result) { 218 Log.d(TAG, "Initial entitlement result: " + result); 219 if (result == null) { 220 showGeneralErrorUi(); 221 finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__FAILED); 222 return; 223 } 224 if (isActivationFlow()) { 225 handleEntitlementStatusForActivation(result); 226 } else { 227 handleEntitlementStatusForUpdating(result); 228 } 229 } 230 231 @MainThread handleEntitlementStatusForActivation(EntitlementResult result)232 private void handleEntitlementStatusForActivation(EntitlementResult result) { 233 Ts43VowifiStatus vowifiStatus = result.getVowifiStatus(); 234 if (vowifiStatus.vowifiEntitled()) { 235 finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__SUCCESSFUL); 236 mActivationUi.setResultAndFinish(Activity.RESULT_OK); 237 } else { 238 if (vowifiStatus.serverDataMissing()) { 239 if (!TextUtils.isEmpty(result.getTermsAndConditionsWebUrl())) { 240 mActivationUi.showWebview( 241 result.getTermsAndConditionsWebUrl(), /* postData= */ null); 242 } else { 243 mActivationUi.showWebview( 244 result.getEmergencyAddressWebUrl(), 245 result.getEmergencyAddressWebData()); 246 } 247 } else if (vowifiStatus.incompatible()) { 248 finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__INCOMPATIBLE); 249 showErrorUi(R.string.failure_contact_carrier); 250 } else { 251 Log.e(TAG, "Unexpected status. Show error UI."); 252 finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNEXPECTED_RESULT); 253 showGeneralErrorUi(); 254 } 255 } 256 } 257 258 @MainThread handleEntitlementStatusForUpdating(EntitlementResult result)259 private void handleEntitlementStatusForUpdating(EntitlementResult result) { 260 Ts43VowifiStatus vowifiStatus = result.getVowifiStatus(); 261 if (vowifiStatus.vowifiEntitled()) { 262 int launchIntention = ActivityConstants.getLaunchIntention(mStartIntent); 263 if (launchIntention == ActivityConstants.LAUNCH_APP_SHOW_TC) { 264 mActivationUi.showWebview( 265 result.getTermsAndConditionsWebUrl(), /* postData= */ null); 266 } else { 267 mActivationUi.showWebview( 268 result.getEmergencyAddressWebUrl(), result.getEmergencyAddressWebData()); 269 } 270 } else { 271 if (vowifiStatus.incompatible()) { 272 showErrorUi(R.string.failure_contact_carrier); 273 mImsUtils.turnOffWfc( 274 () -> finishStatsLog( 275 IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__INCOMPATIBLE) 276 ); 277 } else { 278 Log.e(TAG, "Unexpected status. Show error UI."); 279 finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNEXPECTED_RESULT); 280 showGeneralErrorUi(); 281 } 282 } 283 } 284 285 @MainThread handleReevaluationEntitlementStatus(@ullable EntitlementResult result)286 private void handleReevaluationEntitlementStatus(@Nullable EntitlementResult result) { 287 Log.d(TAG, "Reevaluation entitlement result: " + result); 288 if (result == null) { // Network issue 289 showGeneralErrorUi(); 290 finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__FAILED); 291 return; 292 } 293 if (isActivationFlow()) { 294 handleEntitlementStatusAfterActivation(result); 295 } else { 296 handleEntitlementStatusAfterUpdating(result); 297 } 298 } 299 300 @MainThread handleEntitlementStatusAfterActivation(EntitlementResult result)301 private void handleEntitlementStatusAfterActivation(EntitlementResult result) { 302 Ts43VowifiStatus vowifiStatus = result.getVowifiStatus(); 303 if (vowifiStatus.vowifiEntitled()) { 304 mActivationUi.setResultAndFinish(Activity.RESULT_OK); 305 finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__SUCCESSFUL); 306 } else { 307 if (vowifiStatus.serverDataMissing()) { 308 // Check again after 5s, max retry 6 times 309 if (mEvaluateTimes < ENTITLEMENT_STATUS_UPDATE_RETRY_MAX) { 310 mEvaluateTimes += 1; 311 postDelay( 312 getEntitlementStatusUpdateRetryIntervalMs(), 313 this::reevaluateEntitlementStatus); 314 } else { 315 mEvaluateTimes = 0; 316 showGeneralErrorUi(); 317 finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__TIMEOUT); 318 } 319 } else { 320 // These should never happen, but nothing else we can do. Show general error. 321 Log.e(TAG, "Unexpected status. Show error UI."); 322 showGeneralErrorUi(); 323 finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNEXPECTED_RESULT); 324 } 325 } 326 } 327 getEntitlementStatusUpdateRetryIntervalMs()328 private long getEntitlementStatusUpdateRetryIntervalMs() { 329 return ENTITLEMENT_STATUS_UPDATE_RETRY_INTERVAL_MS; 330 } 331 332 @MainThread handleEntitlementStatusAfterUpdating(EntitlementResult result)333 private void handleEntitlementStatusAfterUpdating(EntitlementResult result) { 334 Ts43VowifiStatus vowifiStatus = result.getVowifiStatus(); 335 if (vowifiStatus.vowifiEntitled()) { 336 mActivationUi.setResultAndFinish(Activity.RESULT_OK); 337 finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__SUCCESSFUL); 338 } else if (vowifiStatus.serverDataMissing()) { 339 // Some carrier allows de-activating in updating flow. 340 mImsUtils.turnOffWfc( 341 () -> { 342 finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__DISABLED); 343 mActivationUi.setResultAndFinish(Activity.RESULT_OK); 344 }); 345 } else { 346 Log.e(TAG, "Unexpected status. Show error UI."); 347 showGeneralErrorUi(); 348 finishStatsLog(IMS_SERVICE_ENTITLEMENT_UPDATED__APP_RESULT__UNEXPECTED_RESULT); 349 } 350 } 351 352 /** Runs {@code action} on caller's thread after {@code delayMillis} ms. */ postDelay(long delayMillis, Runnable action)353 private static void postDelay(long delayMillis, Runnable action) { 354 new CountDownTimer(delayMillis, delayMillis + 100) { 355 // Use a countDownInterval bigger than millisInFuture so onTick never fires. 356 @Override 357 public void onTick(long millisUntilFinished) { 358 // Do nothing 359 } 360 361 @Override 362 public void onFinish() { 363 action.run(); 364 } 365 }.start(); 366 } 367 finishStatsLog(int result)368 private void finishStatsLog(int result) { 369 mAppResult = result; 370 } 371 } 372