1 /*
2  * Copyright 2024 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 static android.webkit.WebSettings.LOAD_DEFAULT;
20 import static android.widget.Toast.LENGTH_SHORT;
21 
22 import android.os.Bundle;
23 import android.util.Log;
24 import android.view.View;
25 import android.webkit.WebStorage;
26 import android.webkit.WebView;
27 import android.webkit.WebViewClient;
28 import android.widget.Button;
29 import android.widget.Toast;
30 
31 import androidx.appcompat.app.AppCompatActivity;
32 import androidx.webkit.WebStorageCompat;
33 import androidx.webkit.WebViewFeature;
34 
35 import org.jspecify.annotations.NonNull;
36 import org.jspecify.annotations.Nullable;
37 
38 import java.io.BufferedReader;
39 import java.io.IOException;
40 import java.io.InputStreamReader;
41 import java.text.SimpleDateFormat;
42 import java.util.Date;
43 import java.util.Locale;
44 import java.util.concurrent.ExecutionException;
45 import java.util.concurrent.Executors;
46 import java.util.concurrent.FutureTask;
47 import java.util.concurrent.TimeUnit;
48 import java.util.concurrent.TimeoutException;
49 
50 import okhttp3.mockwebserver.Dispatcher;
51 import okhttp3.mockwebserver.MockResponse;
52 import okhttp3.mockwebserver.MockWebServer;
53 import okhttp3.mockwebserver.RecordedRequest;
54 
55 public class WebStorageCompatActivity extends AppCompatActivity {
56 
57     private static final String TAG = "WebStorageActivity";
58 
59     private WebView mWebView;
60     private MockWebServer mMockWebServer;
61 
62     private String mPageUrl;
63 
64     @Override
onCreate(@ullable Bundle savedInstanceState)65     protected void onCreate(@Nullable Bundle savedInstanceState) {
66         super.onCreate(savedInstanceState);
67         setContentView(R.layout.activity_web_storage);
68         setTitle(R.string.web_storage_activity_title);
69         WebkitHelpers.appendWebViewVersionToTitle(this);
70         if (!WebViewFeature.isFeatureSupported(WebViewFeature.DELETE_BROWSING_DATA)) {
71             WebkitHelpers.showMessageInActivity(WebStorageCompatActivity.this,
72                     R.string.webkit_api_not_available);
73             return;
74         }
75 
76 
77         mWebView = findViewById(R.id.web_storage_webview);
78         mWebView.getSettings().setCacheMode(LOAD_DEFAULT);
79         mWebView.setWebViewClient(new WebViewClient());
80 
81         Button loadButton = findViewById(R.id.web_storage_load_page_button);
82         loadButton.setOnClickListener(this::onLoadButton);
83 
84         Button deleteButton = findViewById(R.id.web_storage_delete_data_button);
85         deleteButton.setOnClickListener(this::onDeleteButton);
86 
87 
88         mMockWebServer = new MockWebServer();
89 
90         mMockWebServer.setDispatcher(new Dispatcher() {
91             @Override
92             public MockResponse dispatch(RecordedRequest request) {
93                 MockResponse response = new MockResponse();
94                 if (!request.getPath().equals("/")) {
95                     response.setResponseCode(400);
96                     return response;
97                 }
98 
99                 try {
100                     String template = readHtmlTemplate();
101                     response.setHeader("Cache-Control", "max-age=604800");
102                     SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z",
103                             Locale.US);
104                     response.setBody(String.format(template, dateFormat.format(new Date())));
105                 } catch (IOException e) {
106                     Log.e(TAG, "Error loading html template", e);
107                     response.setResponseCode(500);
108                     response.setBody("Error loading html template");
109                 }
110                 return response;
111             }
112         });
113 
114         mPageUrl = startMockServerAndGetPageUrl();
115         mWebView.loadUrl(mPageUrl);
116     }
117 
startMockServerAndGetPageUrl()118     private String startMockServerAndGetPageUrl() {
119         // The mockWebServer accesses networking APIs during startup and URL construction that
120         // are not allowed on the main thread.
121         FutureTask<String> urlTask = new FutureTask<>(() -> {
122             try {
123                 mMockWebServer.start();
124                 return mMockWebServer.url("/").toString();
125             } catch (IOException e) {
126                 throw new RuntimeException(e);
127             }
128         });
129         Executors.newCachedThreadPool().execute(urlTask);
130         try {
131             return urlTask.get(1, TimeUnit.SECONDS);
132         } catch (ExecutionException | InterruptedException | TimeoutException e) {
133             throw new RuntimeException(e);
134         }
135     }
136 
137     @NonNull
readHtmlTemplate()138     private String readHtmlTemplate() throws IOException {
139         try (BufferedReader reader = new BufferedReader(new InputStreamReader(
140                 getResources().openRawResource(R.raw.web_storage_html_template)))) {
141             StringBuilder sb = new StringBuilder();
142             String line;
143             while ((line = reader.readLine()) != null) {
144                 sb.append(line).append("\n");
145             }
146             return sb.toString();
147         }
148     }
149 
150     @Override
onDestroy()151     protected void onDestroy() {
152         super.onDestroy();
153         if (mMockWebServer != null) {
154             try {
155                 mMockWebServer.close();
156             } catch (IOException e) {
157                 Log.e(TAG, "Error closing mock web server", e);
158             }
159         }
160     }
161 
onLoadButton(View v)162     private void onLoadButton(View v) {
163         mWebView.loadUrl(mPageUrl);
164     }
165 
onDeleteButton(View v)166     private void onDeleteButton(View v) {
167         WebStorageCompat.deleteBrowsingData(WebStorage.getInstance(), this::onDeletionComplete);
168     }
169 
onDeletionComplete()170     private void onDeletionComplete() {
171         Toast.makeText(this, R.string.web_storage_delete_complete, LENGTH_SHORT).show();
172     }
173 }
174