1 /* 2 * Copyright (C) 2022 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 package com.android.telephony.qns.wfc; 17 18 import static android.os.AsyncTask.THREAD_POOL_EXECUTOR; 19 20 import android.annotation.Nullable; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.net.ConnectivityManager; 24 import android.net.NetworkInfo; 25 import android.os.Handler; 26 import android.os.Looper; 27 import android.os.Message; 28 import android.telephony.AccessNetworkConstants; 29 import android.telephony.SubscriptionManager; 30 import android.telephony.ims.ImsException; 31 import android.telephony.ims.ImsMmTelManager; 32 import android.telephony.ims.ImsReasonInfo; 33 34 import android.util.Log; 35 36 import com.android.internal.annotations.VisibleForTesting; 37 import com.android.telephony.qns.R; 38 39 import java.util.concurrent.Executor; 40 41 /** A class with helper methods for WfcActivationCanadaActivity */ 42 public class WfcActivationHelper { 43 private static final String TAG = WfcActivationActivity.TAG; 44 45 @VisibleForTesting static final int PRE_EPDG_CONNECTION_DELAY_MS = 1000; // 1 second 46 47 // Enums for Wi-Fi check result 48 public static final int WIFI_CONNECTION_SUCCESS = 0; 49 public static final int WIFI_CONNECTION_ERROR = 1; 50 51 // Enums for ePDG connection result 52 public static final int EPDG_CONNECTION_SUCCESS = 0; 53 public static final int EPDG_CONNECTION_ERROR = 1; 54 55 // Event IDs for ePDG connection 56 @VisibleForTesting static final int EVENT_PRE_START_ATTEMPT = 0; 57 @VisibleForTesting static final int EVENT_START_ATTEMPT = 1; 58 @VisibleForTesting static final int EVENT_FINISH_ATTEMPT = 2; 59 private static final int EVENT_RESULT_SUCCESS = 3; 60 private static final int EVENT_TIMEOUT = 4; 61 private static final int EVENT_RESULT_FAILURE_IKEV2 = 5; 62 private static final int EVENT_RESULT_FAILURE_OTHER = 6; 63 64 public static final String ACTION_TRY_WFC_CONNECTION = 65 "com.android.qns.wfcactivation.TRY_WFC_CONNECTION"; 66 public static final String EXTRA_SUB_ID = "SUB_ID"; 67 public static final String EXTRA_TRY_STATUS = "TRY_STATUS"; 68 public static final int STATUS_START = 1; 69 public static final int STATUS_END = 2; 70 71 // Dependencies 72 private final Context mContext; 73 private final ConnectivityManager mConnectivityManager; 74 private final ImsMmTelManager mImsMmTelManager; 75 private final WfcCarrierConfigManager mWfcConfigManager; 76 77 private final int mSubId; 78 private final Executor mBackgroundExecutor; 79 WfcActivationHelper(Context context, int subId)80 public WfcActivationHelper(Context context, int subId) { 81 this( 82 context, 83 subId, 84 context.getSystemService(ConnectivityManager.class), 85 WfcUtils.getImsMmTelManager(subId), 86 new WfcCarrierConfigManager(context.getApplicationContext(), subId), 87 THREAD_POOL_EXECUTOR); 88 } 89 90 @VisibleForTesting WfcActivationHelper( Context context, int subId, ConnectivityManager cm, @Nullable ImsMmTelManager imsMmTelManager, WfcCarrierConfigManager wfcConfigManager, Executor backgroundExecutor)91 WfcActivationHelper( 92 Context context, 93 int subId, 94 ConnectivityManager cm, 95 @Nullable ImsMmTelManager imsMmTelManager, 96 WfcCarrierConfigManager wfcConfigManager, 97 Executor backgroundExecutor) { 98 mContext = context; 99 mSubId = subId; 100 mConnectivityManager = cm; 101 mImsMmTelManager = imsMmTelManager; 102 mWfcConfigManager = wfcConfigManager; 103 mBackgroundExecutor = backgroundExecutor; 104 mWfcConfigManager.loadConfigurations(); 105 } 106 107 /** 108 * Check WiFi connection 109 * 110 * @param msg The Message to be send with arg1 = result. Result is one of WIFI_CONNECTION_*. 111 */ checkWiFi(Message msg)112 public void checkWiFi(Message msg) { 113 msg.arg1 = checkWiFiAvailability() ? WIFI_CONNECTION_SUCCESS : WIFI_CONNECTION_ERROR; 114 msg.sendToTarget(); 115 } 116 checkWiFiAvailability()117 private boolean checkWiFiAvailability() { 118 NetworkInfo activeNetwork = mConnectivityManager.getActiveNetworkInfo(); 119 return activeNetwork != null 120 && activeNetwork.isConnected() 121 && activeNetwork.getType() == ConnectivityManager.TYPE_WIFI; 122 } 123 notifyQnsServiceToSetWfcMode(int status)124 private void notifyQnsServiceToSetWfcMode(int status) { 125 String qnsPackage = mContext.getResources().getString(R.string.qns_package); 126 Intent intent = new Intent(ACTION_TRY_WFC_CONNECTION); 127 intent.putExtra(EXTRA_SUB_ID, mSubId); 128 intent.putExtra(EXTRA_TRY_STATUS, status); 129 intent.setPackage(qnsPackage); 130 Log.d(TAG, "notify QNS: subId =" + mSubId + ", status =" + status); 131 mContext.sendBroadcast(intent); 132 } 133 134 // This class is a effectively a one-way state machine that cannot be reset & reused. Each call 135 // of tryEpdgConnectionOverWiFi() creates a new instance of this class. 136 private class EpdgConnectHandler extends Handler { 137 final ImsCallback imsCallback; 138 final Message result; 139 boolean imsCallbackRegistered; 140 boolean waitingForResult; // ImsCallback wil be no-op when this is false 141 EpdgConnectHandler(Looper looper, Message result)142 EpdgConnectHandler(Looper looper, Message result) { 143 super(looper); 144 imsCallback = new ImsCallback(this); 145 this.result = result; 146 } 147 148 @Override handleMessage(Message msg)149 public void handleMessage(Message msg) { 150 switch (msg.what) { 151 case EVENT_PRE_START_ATTEMPT: 152 // The callback must be registered before triggering ePDG connection, because 153 // the very 1st firing of the callback after registering MAY be the last IMS 154 // state. 155 // We assume 1 second is enough for that 1st firing. 156 // This means adding 1s delay to WFC activation flow in all cases, and it should 157 // be fine, given this can only be triggered by user manually and is not 158 // expected to be fast. 159 waitingForResult = false; 160 registerImsRegistrationCallback(); 161 // Populate arg1 to EVENT_START_ATTEMPT message 162 sendMessageDelayed( 163 obtainMessage(EVENT_START_ATTEMPT, msg.arg1, 0), 164 /* delayMillis= */ msg.arg2); 165 break; 166 167 case EVENT_START_ATTEMPT: 168 Log.d(TAG, "Try to setup ePDG connection over WiFi"); 169 waitingForResult = true; 170 171 mBackgroundExecutor.execute( 172 () -> { 173 // WFC: on; WFC preference: WiFi preferred (2) 174 mImsMmTelManager.setVoWiFiNonPersistent(true, 2); 175 // notify IMS to program WFC on and WFC mode as Wi-Fi Preferred 176 notifyQnsServiceToSetWfcMode(STATUS_START); 177 }); 178 179 // Timeout event 180 Log.d(TAG, "Will timeout after " + msg.arg1 + " ms"); 181 sendEmptyMessageDelayed(EVENT_TIMEOUT, /* delayMillis= */ msg.arg1); 182 break; 183 184 case EVENT_TIMEOUT: 185 Log.d(TAG, "Timeout: IKEV2 Auth failure not received."); 186 if (getTimeoutResult() == EPDG_CONNECTION_SUCCESS) { 187 sendEmptyMessage(EVENT_RESULT_SUCCESS); 188 } else { 189 sendEmptyMessage(EVENT_RESULT_FAILURE_IKEV2); 190 } 191 break; 192 193 case EVENT_RESULT_SUCCESS: 194 result.arg1 = EPDG_CONNECTION_SUCCESS; 195 // Clean up and send result 196 sendEmptyMessage(EVENT_FINISH_ATTEMPT); 197 break; 198 199 case EVENT_RESULT_FAILURE_IKEV2: 200 Log.d(TAG, "Turn off WFC"); 201 // WFC: off; WFC preference: cellular preferred (1) 202 mBackgroundExecutor.execute( 203 () -> mImsMmTelManager.setVoWiFiNonPersistent(false, 1)); 204 // Set result: failure 205 result.arg1 = EPDG_CONNECTION_ERROR; 206 // Clean up and send result 207 sendEmptyMessage(EVENT_FINISH_ATTEMPT); 208 break; 209 210 case EVENT_FINISH_ATTEMPT: 211 waitingForResult = false; 212 // Remove timeout event - if we get here via EVENT_TIMEOUT, this do nothing. 213 removeMessages(EVENT_TIMEOUT); 214 // Unregister mImsCallback 215 unregisterImsRegistrationCallback(); 216 mBackgroundExecutor.execute( 217 () -> { 218 // Turn on WFC if success. W/o this, WFC could be turned 219 // ON (by STATUS_START) - OFF (by STATUS_END) - ON (by Settings app) 220 // which causes unnecessary IMS registration traffic. 221 // This must be done before sending STATUS_END so vendor IMS will 222 // see DB value ON. 223 if (result.arg1 == EPDG_CONNECTION_SUCCESS) { 224 Log.d(TAG, "Turn on WFC"); 225 mImsMmTelManager.setVoWiFiSettingEnabled(true); 226 } 227 // Notify IMS to revert WFC on/off and mode to follow user settings. 228 // Notify here to make sure all cases (success, failure, timeout) 229 // reach this line. 230 notifyQnsServiceToSetWfcMode(STATUS_END); 231 // Send result 232 result.sendToTarget(); 233 }); 234 break; 235 236 case EVENT_RESULT_FAILURE_OTHER: 237 break; 238 default: // Do nothing 239 } 240 } 241 registerImsRegistrationCallback()242 private void registerImsRegistrationCallback() { 243 try { 244 Log.d(TAG, "registerImsRegistrationCallback"); 245 mImsMmTelManager.registerImsRegistrationCallback(this::post, imsCallback); 246 imsCallbackRegistered = true; 247 } catch (ImsException | RuntimeException e) { 248 Log.e(TAG, "registerImsRegistrationCallback failed", e); 249 // Fail silently to trigger timeout 250 imsCallbackRegistered = false; 251 } 252 } 253 unregisterImsRegistrationCallback()254 private void unregisterImsRegistrationCallback() { 255 if (!imsCallbackRegistered) { 256 return; 257 } 258 259 try { 260 Log.d(TAG, "unregisterImsRegistrationCallback"); 261 mImsMmTelManager.unregisterImsRegistrationCallback(imsCallback); 262 imsCallbackRegistered = false; 263 } catch (RuntimeException e) { 264 Log.e(TAG, "unregisterImsRegistrationCallback failed", e); 265 } 266 } 267 } 268 269 /** 270 * Try to setup ePDG connection over WiFi. 271 * 272 * @param msg The Message to be send with arg1 = result. Result is one of EPDG_CONNECTION_*. 273 * @param timeoutMs Timeout, in milliseconds, then abort waiting for ePDG connection result. 274 */ tryEpdgConnectionOverWiFi(Message msg, int timeoutMs)275 public void tryEpdgConnectionOverWiFi(Message msg, int timeoutMs) { 276 if (mImsMmTelManager == null) { 277 // Send message with EPDG_CONNECTION_ERROR immediately. 278 Log.e(TAG, "ImsMmTelManager is null"); 279 msg.arg1 = EPDG_CONNECTION_ERROR; 280 msg.sendToTarget(); 281 return; 282 } 283 284 // NOTE: This private handler is hosted on the same looper as msg. 285 EpdgConnectHandler handler = new EpdgConnectHandler(msg.getTarget().getLooper(), msg); 286 // Start attempt of ePDG connection. 287 handler.obtainMessage(EVENT_PRE_START_ATTEMPT, timeoutMs, PRE_EPDG_CONNECTION_DELAY_MS) 288 .sendToTarget(); 289 } 290 291 @VisibleForTesting 292 static class ImsCallback extends ImsMmTelManager.RegistrationCallback { 293 private final EpdgConnectHandler handler; 294 ImsCallback(EpdgConnectHandler handler)295 ImsCallback(EpdgConnectHandler handler) { 296 this.handler = handler; 297 } 298 299 @Override onRegistered(int imsTransportType)300 public void onRegistered(int imsTransportType) { 301 if (!handler.waitingForResult) { 302 return; 303 } 304 if (imsTransportType != AccessNetworkConstants.TRANSPORT_TYPE_WLAN) { 305 return; 306 } 307 Log.d(TAG, "IMS connected on WLAN."); 308 handler.sendEmptyMessage(EVENT_RESULT_SUCCESS); 309 } 310 311 @Override onUnregistered(ImsReasonInfo imsReasonInfo)312 public void onUnregistered(ImsReasonInfo imsReasonInfo) { 313 if (!handler.waitingForResult) { 314 return; 315 } 316 Log.d(TAG, "IMS disconnected: " + imsReasonInfo); 317 if (isIkev2AuthFailure(imsReasonInfo)) { 318 handler.sendEmptyMessage(EVENT_RESULT_FAILURE_IKEV2); 319 } else { 320 handler.obtainMessage( 321 EVENT_RESULT_FAILURE_OTHER, 322 imsReasonInfo.getCode(), 323 imsReasonInfo.getExtraCode()) 324 .sendToTarget(); 325 } 326 } 327 328 @Override onTechnologyChangeFailed(int imsTransportType, ImsReasonInfo imsReasonInfo)329 public void onTechnologyChangeFailed(int imsTransportType, ImsReasonInfo imsReasonInfo) { 330 if (!handler.waitingForResult) { 331 return; 332 } 333 if (imsTransportType != AccessNetworkConstants.TRANSPORT_TYPE_WLAN) { 334 return; 335 } 336 Log.d(TAG, "IMS registration failed on WLAN: " + imsReasonInfo); 337 if (isIkev2AuthFailure(imsReasonInfo)) { 338 handler.sendEmptyMessage(EVENT_RESULT_FAILURE_IKEV2); 339 } else { 340 handler.obtainMessage( 341 EVENT_RESULT_FAILURE_OTHER, 342 imsReasonInfo.getCode(), 343 imsReasonInfo.getExtraCode()) 344 .sendToTarget(); 345 } 346 } 347 } 348 isIkev2AuthFailure(ImsReasonInfo imsReasonInfo)349 static boolean isIkev2AuthFailure(ImsReasonInfo imsReasonInfo) { 350 if (imsReasonInfo.getCode() == ImsReasonInfo.CODE_EPDG_TUNNEL_ESTABLISH_FAILURE) { 351 if (imsReasonInfo.getExtraCode() == ImsReasonInfo.CODE_IKEV2_AUTH_FAILURE) { 352 return true; 353 } 354 } 355 return false; 356 } 357 getTimeoutResult()358 private int getTimeoutResult() { 359 return mWfcConfigManager.isShowVowifiPortalAfterTimeout() 360 ? EPDG_CONNECTION_ERROR 361 : EPDG_CONNECTION_SUCCESS; 362 } 363 getWebPortalUrl()364 public String getWebPortalUrl() { 365 return mWfcConfigManager.getVowifiEntitlementServerUrl(); 366 } 367 getVowifiRegistrationTimerForVowifiActivation()368 public int getVowifiRegistrationTimerForVowifiActivation() { 369 return mWfcConfigManager.getVowifiRegistrationTimerForVowifiActivation(); 370 } 371 supportJsCallbackForVowifiPortal()372 public boolean supportJsCallbackForVowifiPortal() { 373 return mWfcConfigManager.supportJsCallbackForVowifiPortal(); 374 } 375 } 376