1 /*
2  * Copyright 2019 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.example.androidx.webkit;
18 
19 import android.app.Activity;
20 import android.content.Intent;
21 import android.os.Bundle;
22 import android.util.SparseArray;
23 import android.webkit.WebResourceRequest;
24 import android.webkit.WebView;
25 
26 import androidx.appcompat.app.AppCompatActivity;
27 import androidx.webkit.SafeBrowsingResponseCompat;
28 import androidx.webkit.WebViewClientCompat;
29 import androidx.webkit.WebViewFeature;
30 
31 import org.jspecify.annotations.NonNull;
32 import org.jspecify.annotations.Nullable;
33 
34 /**
35  * An {@link Activity} which shows a custom interstitial if {@link WebView} encounters malicious
36  * resources. This class contains the logic for responding to user interaction with custom
37  * interstitials. The UI for these interstitials is implemented by {@link
38  * PopupInterstitialActivity}.
39  */
40 public class CustomInterstitialActivity extends AppCompatActivity {
41 
42     private WebView mWebView;
43     private CustomInterstitialWebViewClient mWebViewClient;
44 
45     @Override
onCreate(@ullable Bundle savedInstanceState)46     protected void onCreate(@Nullable Bundle savedInstanceState) {
47         super.onCreate(savedInstanceState);
48         setContentView(R.layout.activity_custom_interstitial);
49         setTitle(R.string.custom_interstitial_activity_title);
50         WebkitHelpers.appendWebViewVersionToTitle(this);
51 
52         if (WebViewFeature.isFeatureSupported(WebViewFeature.SAFE_BROWSING_HIT)
53                 && WebViewFeature.isFeatureSupported(WebViewFeature.SAFE_BROWSING_RESPONSE_PROCEED)
54                 && WebViewFeature.isFeatureSupported(
55                 WebViewFeature.SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY)) {
56             mWebView = findViewById(R.id.custom_interstitial_webview);
57             mWebViewClient = new CustomInterstitialWebViewClient(this);
58             mWebView.setWebViewClient(mWebViewClient);
59             mWebView.loadUrl(SafeBrowsingHelpers.TEST_SAFE_BROWSING_SITE);
60         } else {
61             WebkitHelpers.showMessageInActivity(this, R.string.webkit_api_not_available);
62         }
63     }
64 
65     @Override
onActivityResult(int requestCode, int resultCode, Intent data)66     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
67         super.onActivityResult(requestCode, resultCode, data);
68         mWebViewClient.handleInterstitialResponse(requestCode, resultCode, data);
69     }
70 
71     @SuppressWarnings("deprecation")
72     @Override
onBackPressed()73     public void onBackPressed() {
74         if (mWebView.canGoBack()) {
75             mWebView.goBack();
76         } else {
77             super.onBackPressed();
78         }
79     }
80 
81     private static class CustomInterstitialWebViewClient extends WebViewClientCompat {
82         private final Activity mActivity;
83         private final SparseArray<SafeBrowsingResponseCompat> mSafeBrowsingResponseMap;
84         int mActivityRequestCounter;
85 
CustomInterstitialWebViewClient(Activity activity)86         CustomInterstitialWebViewClient(Activity activity) {
87             mActivity = activity;
88             mSafeBrowsingResponseMap = new SparseArray<>();
89             mActivityRequestCounter = 0;
90         }
91 
92         @Override
onSafeBrowsingHit(@onNull WebView view, @NonNull WebResourceRequest request, int threatType, @NonNull SafeBrowsingResponseCompat callback)93         public void onSafeBrowsingHit(@NonNull WebView view, @NonNull WebResourceRequest request,
94                 int threatType, @NonNull SafeBrowsingResponseCompat callback) {
95             mSafeBrowsingResponseMap.put(mActivityRequestCounter, callback);
96             createInterstitial(threatType, request);
97             mActivityRequestCounter++;
98         }
99 
createInterstitial(int threatType, @NonNull WebResourceRequest request)100         private void createInterstitial(int threatType, @NonNull WebResourceRequest request) {
101             Intent myIntent = new Intent(mActivity, PopupInterstitialActivity.class);
102             myIntent.putExtra(PopupInterstitialActivity.THREAT_TYPE, threatType);
103             myIntent.putExtra(PopupInterstitialActivity.THREAT_URL,
104                     request.getUrl().toString());
105             mActivity.startActivityForResult(myIntent, mActivityRequestCounter);
106         }
107 
handleInterstitialResponse(int requestCode, int resultCode, Intent data)108         void handleInterstitialResponse(int requestCode, int resultCode, Intent data) {
109             // Get the correct SafeBrowsingResponse for the given interstitial Intent (there can be
110             // multiple Intents at the same time if multiple resources are malicious).
111             final SafeBrowsingResponseCompat response = mSafeBrowsingResponseMap.get(requestCode);
112             mSafeBrowsingResponseMap.delete(requestCode);
113 
114             // Make sure the request was successful
115             if (resultCode == RESULT_OK) {
116                 if (response == null) {
117                     return;
118                 }
119 
120                 // Figure out what navigation action we should take.
121                 String result = data.getStringExtra(PopupInterstitialActivity.ACTION_RESPONSE);
122                 // Figure out whether we should report this event.
123                 boolean shouldSendReport = data.getBooleanExtra(
124                         PopupInterstitialActivity.SHOULD_SEND_REPORT, false);
125 
126                 switch (result) {
127                     case PopupInterstitialActivity.ACTION_RESPONSE_BACK_TO_SAFETY:
128                         response.backToSafety(shouldSendReport);
129                         break;
130                     case PopupInterstitialActivity.ACTION_RESPONSE_PROCEED:
131                         response.proceed(shouldSendReport);
132                         break;
133                     default:
134                         break;
135                 }
136             } else if (resultCode == RESULT_CANCELED) {
137                 // User pressed the system's back button, treat this like backToSafety().
138                 response.backToSafety(false);
139             } else {
140                 throw new IllegalStateException("PopupInterstitialActivity shouldn't return any "
141                         + "nonstandard resultCodes");
142             }
143         }
144 
145     }
146 }
147