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                         if (!v.hasFocus()) {
99                             v.requestFocus();
100                         }
101                     }
102 
103                     @Override
104                     public void onViewDetachedFromWindow(View v) {
105                         Log.d(TAG, "#onViewDetachedFromWindow");
106                         if (!mFinishFlow) {
107                             ((WfcActivationUi) getActivity()).setResultAndFinish(
108                                     Activity.RESULT_CANCELED);
109                         }
110                     }
111                 });
112         mWebView.addJavascriptInterface(new JsInterface(getActivity()), JS_CONTROLLER_NAME);
113         WebSettings settings = mWebView.getSettings();
114         settings.setDomStorageEnabled(true);
115         settings.setJavaScriptEnabled(true);
116 
117         if (TextUtils.isEmpty(postData)) {
118             mWebView.loadUrl(url);
119         } else {
120             mWebView.postUrl(url, postData.getBytes());
121         }
122         return v;
123     }
124 
125     /**
126      * To support webview handle back key to go back previous page.
127      *
128      * @return {@code true} let activity not do anything for this key down.
129      *         {@code false} activity should handle key down.
130      */
onKeyDown(int keyCode, KeyEvent keyEvent)131     public boolean onKeyDown(int keyCode, KeyEvent keyEvent) {
132         if (keyCode == KeyEvent.KEYCODE_BACK && keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
133             if (mWebView != null
134                     && mWebView.canGoBack()
135                     && mWebView.getUrl().toLowerCase().endsWith(URL_WITH_PDF_FILE_EXTENSION)) {
136                 mWebView.goBack();
137                 return true;
138             }
139         }
140         return false;
141     }
142 
143     /** Emergency address websheet javascript callback. */
144     private class JsInterface {
145         private final WfcActivationUi mUi;
146         private final Executor mMainExecutor;
147 
JsInterface(Activity activity)148         JsInterface(Activity activity) {
149             mUi = (WfcActivationUi) activity;
150             mMainExecutor = activity.getMainExecutor();
151         }
152 
153         /**
154          * Callback function when the VoWiFi service flow ends properly between the device and the
155          * VoWiFi portal web server.
156          */
157         @JavascriptInterface
entitlementChanged()158         public void entitlementChanged() {
159             Log.d(TAG, "#entitlementChanged");
160             mFinishFlow = true;
161             mMainExecutor.execute(() -> mUi.getController().finishFlow());
162         }
163 
164         /**
165          * Callback function when the VoWiFi service flow ends prematurely, either by user
166          * action or due to a web sheet or network error.
167          */
168         @JavascriptInterface
dismissFlow()169         public void dismissFlow() {
170             Log.d(TAG, "#dismissFlow");
171             mUi.setResultAndFinish(Activity.RESULT_CANCELED);
172         }
173     }
174 }
175