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 android.app.Activity;
20 import android.graphics.Bitmap;
21 import android.os.Bundle;
22 import android.text.TextUtils;
23 import android.util.Log;
24 import android.view.KeyEvent;
25 import android.view.LayoutInflater;
26 import android.view.View;
27 import android.view.View.OnAttachStateChangeListener;
28 import android.view.ViewGroup;
29 import android.webkit.JavascriptInterface;
30 import android.webkit.WebSettings;
31 import android.webkit.WebView;
32 import android.webkit.WebViewClient;
33 import android.widget.ProgressBar;
34 
35 import androidx.fragment.app.Fragment;
36 
37 import java.util.concurrent.Executor;
38 
39 /** A fragment of WebView to render WFC T&C and emergency address web portal */
40 public class WfcWebPortalFragment extends Fragment {
41     private static final String TAG = "IMSSE-WfcWebPortalFragment";
42 
43     private static final String KEY_URL_STRING = "url";
44     private static final String KEY_POST_DATA_STRING = "post_data";
45     // Javascript object associated with the webview callback functions. See TS.43 v5.0 section 3.4
46     private static final String JS_CONTROLLER_NAME = "VoWiFiWebServiceFlow";
47     private static final String URL_WITH_PDF_FILE_EXTENSION = ".pdf";
48 
49     private WebView mWebView;
50     private boolean mFinishFlow = false;
51 
52     /** Public static constructor */
newInstance(String url, String postData)53     public static WfcWebPortalFragment newInstance(String url, String postData) {
54         WfcWebPortalFragment frag = new WfcWebPortalFragment();
55 
56         Bundle args = new Bundle();
57         args.putString(KEY_URL_STRING, url);
58         args.putString(KEY_POST_DATA_STRING, postData);
59         frag.setArguments(args);
60 
61         return frag;
62     }
63 
64     @Override
onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)65     public View onCreateView(
66             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
67         View v = inflater.inflate(R.layout.fragment_webview, container, false);
68 
69         Bundle arguments = getArguments();
70         Log.d(TAG, "Webview arguments: " + arguments);
71         String url = arguments.getString(KEY_URL_STRING, "");
72         String postData = arguments.getString(KEY_POST_DATA_STRING, "");
73 
74         ProgressBar spinner = v.findViewById(R.id.loadingbar);
75         mWebView = v.findViewById(R.id.webview);
76         mWebView.setWebViewClient(
77                 new WebViewClient() {
78                     @Override
79                     public boolean shouldOverrideUrlLoading(WebView view, String url) {
80                         return false; // Let WebView handle redirected URL
81                     }
82 
83                     @Override
84                     public void onPageStarted(WebView view, String url, Bitmap favicon) {
85                         spinner.setVisibility(View.VISIBLE);
86                     }
87 
88                     @Override
89                     public void onPageFinished(WebView view, String url) {
90                         spinner.setVisibility(View.GONE);
91                         super.onPageFinished(view, url);
92                     }
93                 });
94         mWebView.addOnAttachStateChangeListener(
95                 new OnAttachStateChangeListener() {
96                     @Override
97                     public void onViewAttachedToWindow(View v) {
98                     }
99 
100                     @Override
101                     public void onViewDetachedFromWindow(View v) {
102                         Log.d(TAG, "#onViewDetachedFromWindow");
103                         if (!mFinishFlow) {
104                             ((WfcActivationUi) getActivity()).setResultAndFinish(
105                                     Activity.RESULT_CANCELED);
106                         }
107                     }
108                 });
109         mWebView.addJavascriptInterface(new JsInterface(getActivity()), JS_CONTROLLER_NAME);
110         WebSettings settings = mWebView.getSettings();
111         settings.setDomStorageEnabled(true);
112         settings.setJavaScriptEnabled(true);
113 
114         if (TextUtils.isEmpty(postData)) {
115             mWebView.loadUrl(url);
116         } else {
117             mWebView.postUrl(url, postData.getBytes());
118         }
119         return v;
120     }
121 
122     /**
123      * To support webview handle back key to go back previous page.
124      *
125      * @return {@code true} let activity not do anything for this key down.
126      *         {@code false} activity should handle key down.
127      */
onKeyDown(int keyCode, KeyEvent keyEvent)128     public boolean onKeyDown(int keyCode, KeyEvent keyEvent) {
129         if (keyCode == KeyEvent.KEYCODE_BACK && keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
130             if (mWebView != null
131                     && mWebView.canGoBack()
132                     && mWebView.getUrl().toLowerCase().endsWith(URL_WITH_PDF_FILE_EXTENSION)) {
133                 mWebView.goBack();
134                 return true;
135             }
136         }
137         return false;
138     }
139 
140     /** Emergency address websheet javascript callback. */
141     private class JsInterface {
142         private final WfcActivationUi mUi;
143         private final Executor mMainExecutor;
144 
JsInterface(Activity activity)145         JsInterface(Activity activity) {
146             mUi = (WfcActivationUi) activity;
147             mMainExecutor = activity.getMainExecutor();
148         }
149 
150         /**
151          * Callback function when the VoWiFi service flow ends properly between the device and the
152          * VoWiFi portal web server.
153          */
154         @JavascriptInterface
entitlementChanged()155         public void entitlementChanged() {
156             Log.d(TAG, "#entitlementChanged");
157             mFinishFlow = true;
158             mMainExecutor.execute(() -> mUi.getController().finishFlow());
159         }
160 
161         /**
162          * Callback function when the VoWiFi service flow ends prematurely, either by user
163          * action or due to a web sheet or network error.
164          */
165         @JavascriptInterface
dismissFlow()166         public void dismissFlow() {
167             Log.d(TAG, "#dismissFlow");
168             mUi.setResultAndFinish(Activity.RESULT_CANCELED);
169         }
170     }
171 }
172