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 package com.android.telephony.qns.wfc; 17 18 import android.app.Activity; 19 import android.app.AlertDialog; 20 import android.app.Dialog; 21 import android.app.ProgressDialog; 22 import android.annotation.Nullable; 23 import android.content.ComponentName; 24 import android.content.DialogInterface.OnClickListener; 25 import android.content.Intent; 26 import android.net.Uri; 27 import android.os.Bundle; 28 import android.os.Handler; 29 import android.os.Message; 30 import android.provider.Settings; 31 import android.text.TextUtils; 32 import android.util.Log; 33 import android.view.ContextThemeWrapper; 34 import android.view.WindowManager; 35 36 import androidx.activity.result.ActivityResultLauncher; 37 import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; 38 import androidx.browser.customtabs.CustomTabsClient; 39 import androidx.browser.customtabs.CustomTabsIntent; 40 import androidx.browser.customtabs.CustomTabsServiceConnection; 41 import androidx.browser.customtabs.CustomTabsSession; 42 import androidx.fragment.app.DialogFragment; 43 import androidx.fragment.app.FragmentActivity; 44 45 import com.android.internal.annotations.VisibleForTesting; 46 import com.android.telephony.qns.R; 47 48 /** Main activity to handle VoWiFi activation */ 49 public class WfcActivationActivity extends FragmentActivity { 50 51 public static final String TAG = "QNS-WfcActivationActivity"; 52 53 private static final String EXTRA_URL = "EXTRA_URL"; 54 55 // Message IDs 56 private static final int MESSAGE_CHECK_WIFI = 1; 57 private static final int MESSAGE_CHECK_WIFI_DONE = 2; 58 private static final int MESSAGE_TRY_EPDG_CONNECTION = 3; 59 private static final int MESSAGE_TRY_EPDG_CONNECTION_DONE = 4; 60 private static final int MESSAGE_SHOW_WEB_PORTAL = 5; 61 62 private WfcActivationHelper mWfcActivationHelper; 63 64 private Handler mUiHandler; 65 @VisibleForTesting ProgressDialog mProgressDialog; 66 67 private CustomTabsSession mCustomTabsSession; 68 @VisibleForTesting CustomTabsServiceConnection mServiceConnection; 69 private ActivityResultLauncher<Intent> mWebviewResultsLauncher = 70 registerForActivityResult( 71 new StartActivityForResult(), 72 activityResult -> { 73 if (activityResult.getResultCode() == Activity.RESULT_CANCELED) { 74 Log.d(TAG, "Webview Activity Result CANCEL"); 75 finish(); 76 } else { 77 Log.d(TAG, "Webview Activity Result OK"); 78 finish(); 79 } 80 }); 81 82 // Whether it's safe now to update UI, based on activity visibility. 83 // It should be true between onResume() and onPause(). 84 private boolean mSafeToUpdateUi = false; 85 86 @Override onCreate(Bundle savedInstanceState)87 protected void onCreate(Bundle savedInstanceState) { 88 // Initialization 89 super.onCreate(savedInstanceState); 90 createDependencies(); 91 createUiHandler(); 92 93 // Set layout 94 setContentView(R.layout.activity_wfc_activation); 95 96 if (WfcUtils.isActivationFlow(getIntent())) { 97 // WFC activation flow 98 mUiHandler.sendEmptyMessage(MESSAGE_CHECK_WIFI); 99 } else { 100 // Emergency address update flow 101 mUiHandler.sendEmptyMessage(MESSAGE_SHOW_WEB_PORTAL); 102 } 103 } 104 105 @Override onResume()106 protected void onResume() { 107 super.onResume(); 108 mSafeToUpdateUi = true; 109 } 110 111 @Override onPause()112 protected void onPause() { 113 super.onPause(); 114 mSafeToUpdateUi = false; 115 } 116 createUiHandler()117 private void createUiHandler() { 118 Handler.Callback handlerCallback = 119 (Message msg) -> { 120 Log.d(TAG, "UiHandler received: " + msg); 121 switch (msg.what) { 122 case MESSAGE_CHECK_WIFI: 123 mWfcActivationHelper.checkWiFi( 124 mUiHandler.obtainMessage(MESSAGE_CHECK_WIFI_DONE)); 125 break; 126 case MESSAGE_CHECK_WIFI_DONE: 127 if (msg.arg1 == WfcActivationHelper.WIFI_CONNECTION_SUCCESS) { 128 mUiHandler.sendEmptyMessage(MESSAGE_TRY_EPDG_CONNECTION); 129 } else { // msg.arg1 == WfcActivationHelper.WIFI_CONNECTION_ERROR 130 showWiFiUnavailableDialog(); 131 } 132 break; 133 case MESSAGE_TRY_EPDG_CONNECTION: 134 showProgressDialog(); 135 Log.d(TAG, "Show progress dialog - tryEpdgConnectionOverWiFi"); 136 mWfcActivationHelper.tryEpdgConnectionOverWiFi( 137 mUiHandler.obtainMessage(MESSAGE_TRY_EPDG_CONNECTION_DONE), 138 mWfcActivationHelper 139 .getVowifiRegistrationTimerForVowifiActivation()); 140 break; 141 case MESSAGE_TRY_EPDG_CONNECTION_DONE: 142 dismissProgressDialog(); 143 Log.d(TAG, "Dismiss progress dialog - tryEpdgConnectionOverWiFi"); 144 if (msg.arg1 == WfcActivationHelper.EPDG_CONNECTION_SUCCESS) { 145 Log.d(TAG, "VoWiFi activated"); 146 setResultAndFinish(RESULT_OK); 147 } else { // msg.arg1 == WfcActivationHelper.EPDG_CONNECTION_ERROR 148 mUiHandler.sendEmptyMessage(MESSAGE_SHOW_WEB_PORTAL); 149 } 150 break; 151 case MESSAGE_SHOW_WEB_PORTAL: 152 startWebPortal(); 153 break; 154 default: 155 Log.e(TAG, "UiHandler received unknown message: " + msg); 156 return false; 157 } 158 return true; 159 }; 160 mUiHandler = new Handler(handlerCallback); 161 } 162 163 @Override onDestroy()164 public void onDestroy() { 165 if (mServiceConnection != null) { 166 unbindService(mServiceConnection); 167 } 168 super.onDestroy(); 169 } 170 startWebPortal()171 private void startWebPortal() { 172 Log.d(TAG, "starting web portal .."); 173 if (!mSafeToUpdateUi) { 174 Log.d(TAG, "Not safe to update UI. Stopping."); 175 return; 176 } 177 String url = mWfcActivationHelper.getWebPortalUrl(); 178 if (TextUtils.isEmpty(url)) { 179 Log.d(TAG, "No web portal url!"); 180 return; 181 } 182 if (!mWfcActivationHelper.supportJsCallbackForVowifiPortal()) { 183 // For carriers not requiring JS callback in their WFC activation webpage, using a 184 // ChromeCustomTab provides richer web functionality while avoiding jumping to the browser 185 // app and introducing a discontinuity in UX. 186 startCustomTab(url); 187 } else { 188 // Because QNS uses system UID now, webview cannot be started here. Instead, webview is 189 // started in a different activity, {@code R.string.webview_component}. 190 startWebPortalActivity(); 191 } 192 } 193 startCustomTab(String url)194 private void startCustomTab(String url) { 195 mServiceConnection = 196 new CustomTabsServiceConnection() { 197 @Override 198 public void onCustomTabsServiceConnected( 199 ComponentName name, CustomTabsClient client) { 200 client.warmup(0L); 201 mCustomTabsSession = client.newSession(null); 202 mCustomTabsSession.mayLaunchUrl(Uri.parse(url), null, null); 203 } 204 205 @Override 206 public void onServiceDisconnected(ComponentName name) { 207 mCustomTabsSession = null; 208 } 209 }; 210 211 String ServicePackageName = 212 getResources().getString(R.string.custom_tabs_service_package_name); 213 CustomTabsClient.bindCustomTabsService(this, ServicePackageName, mServiceConnection); 214 new CustomTabsIntent.Builder(mCustomTabsSession).build().launchUrl(this, Uri.parse(url)); 215 216 if (WfcUtils.isActivationFlow(getIntent())) { 217 setResultAndFinish(RESULT_CANCELED); 218 } else { 219 setResultAndFinish(RESULT_OK); 220 } 221 } startWebPortalActivity()222 private void startWebPortalActivity() { 223 String webviewComponent = getResources().getString(R.string.webview_component); 224 ComponentName componentName = ComponentName.unflattenFromString(webviewComponent); 225 String url = mWfcActivationHelper.getWebPortalUrl(); 226 227 Log.d(TAG, "startWebPortalActivity componentName: " + componentName); 228 Intent intent = new Intent(); 229 intent.setComponent(componentName); 230 intent.putExtra(EXTRA_URL, url); 231 mWebviewResultsLauncher.launch(intent); 232 } 233 showProgressDialog()234 private void showProgressDialog() { 235 if (!mSafeToUpdateUi) { 236 return; 237 } 238 if (mProgressDialog != null && mProgressDialog.isShowing()) { 239 return; 240 } 241 mProgressDialog = 242 new ProgressDialog( 243 new ContextThemeWrapper( 244 this, android.R.style.Theme_DeviceDefault_Light_Dialog)); 245 mProgressDialog.setCancelable(false); 246 mProgressDialog.setCanceledOnTouchOutside(false); 247 mProgressDialog.setMessage(getText(R.string.progress_text)); 248 mProgressDialog.show(); 249 // Keep screen on 250 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 251 } 252 dismissProgressDialog()253 private void dismissProgressDialog() { 254 if (!mSafeToUpdateUi) { 255 return; 256 } 257 if (mProgressDialog != null) { 258 mProgressDialog.dismiss(); 259 mProgressDialog = null; 260 // Allow screen off 261 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 262 } 263 } 264 showWiFiUnavailableDialog()265 private void showWiFiUnavailableDialog() { 266 if (!mSafeToUpdateUi) { 267 return; 268 } 269 DialogFragment dialog = 270 AlertDialogFragment.newInstance( 271 R.string.connect_to_wifi_or_web_portal_title, 272 R.string.connect_to_wifi_or_web_portal_message); 273 dialog.show(getSupportFragmentManager(), "Wifi_unavailable_dialog"); 274 } 275 276 /** Dialog fragment to show error messages */ 277 public static class AlertDialogFragment extends DialogFragment { 278 279 private static final String TITLE_KEY = "TITLE_KEY"; 280 private static final String MESSAGE_KEY = "MESSAGE_KEY"; 281 282 /** Static constructor */ newInstance(int titleId, int messageId)283 public static AlertDialogFragment newInstance(int titleId, int messageId) { 284 AlertDialogFragment frag = new AlertDialogFragment(); 285 frag.setCancelable(false); 286 287 Bundle args = new Bundle(); 288 args.putInt(TITLE_KEY, titleId); 289 args.putInt(MESSAGE_KEY, messageId); 290 frag.setArguments(args); 291 292 return frag; 293 } 294 295 @Override onCreateDialog(Bundle savedInstanceState)296 public Dialog onCreateDialog(Bundle savedInstanceState) { 297 Bundle args = getArguments(); 298 int titleId = args.getInt(TITLE_KEY); 299 int messageId = args.getInt(MESSAGE_KEY); 300 final WfcActivationActivity activity = 301 (WfcActivationActivity) getActivity(); 302 return new AlertDialog.Builder( 303 new ContextThemeWrapper( 304 getActivity(), 305 android.R.style.Theme_DeviceDefault_Light_Dialog)) 306 .setTitle(titleId) 307 .setMessage(messageId) 308 .setPositiveButton( 309 R.string.button_setup_web_portal, 310 (OnClickListener) 311 (dialog, which) -> 312 activity.mUiHandler.sendEmptyMessage( 313 MESSAGE_SHOW_WEB_PORTAL)) 314 .setNegativeButton( 315 R.string.button_turn_on_wifi, 316 (OnClickListener) 317 (dialog, which) -> { 318 // Redirect to WiFi settings UI 319 Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS); 320 activity.startActivity(intent); 321 // And finish self 322 activity.setResultAndFinish(RESULT_CANCELED); 323 }) 324 .create(); 325 } 326 } 327 setResultAndFinish(int resultCode)328 private void setResultAndFinish(int resultCode) { 329 setResult(resultCode); 330 finish(); 331 } 332 createDependencies()333 private void createDependencies() { 334 // Default initialization for production 335 int subId = WfcUtils.getSubId(getIntent()); 336 337 if (WfcUtils.getWfcActivationHelper() != null) { 338 mWfcActivationHelper = WfcUtils.getWfcActivationHelper(); 339 Log.v(TAG, "WfcActivationHelper injected: " + mWfcActivationHelper); 340 } else { 341 mWfcActivationHelper = new WfcActivationHelper(this, subId); 342 } 343 344 if (WfcUtils.getWebviewResultLauncher() != null) { 345 mWebviewResultsLauncher = WfcUtils.getWebviewResultLauncher(); 346 Log.v(TAG, "getWebviewResultLauncher injected: " + mWebviewResultsLauncher); 347 } 348 } 349 } 350