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 static java.nio.charset.StandardCharsets.UTF_8;
20 
21 import android.app.Activity;
22 import android.os.Bundle;
23 import android.view.KeyEvent;
24 import android.view.View;
25 import android.view.inputmethod.EditorInfo;
26 import android.webkit.WebView;
27 import android.webkit.WebViewClient;
28 import android.widget.Button;
29 import android.widget.EditText;
30 import android.widget.TextView;
31 
32 import androidx.appcompat.app.AppCompatActivity;
33 import androidx.webkit.TracingConfig;
34 import androidx.webkit.TracingController;
35 import androidx.webkit.WebViewFeature;
36 
37 import org.json.JSONException;
38 import org.json.JSONObject;
39 import org.jspecify.annotations.Nullable;
40 
41 import java.io.BufferedReader;
42 import java.io.File;
43 import java.io.FileInputStream;
44 import java.io.FileNotFoundException;
45 import java.io.FileOutputStream;
46 import java.io.IOException;
47 import java.io.InputStreamReader;
48 import java.util.concurrent.ExecutorService;
49 import java.util.concurrent.Executors;
50 
51 /**
52  * An {@link Activity} to exercise Tracing Controller functionality.
53  */
54 public class TracingControllerActivity extends AppCompatActivity {
55 
56     @Override
57     @SuppressWarnings("CatchAndPrintStackTrace")
onCreate(@ullable Bundle savedInstanceState)58     protected void onCreate(@Nullable Bundle savedInstanceState) {
59         super.onCreate(savedInstanceState);
60         setContentView(R.layout.activity_tracing_controller);
61         setTitle(R.string.tracing_controller_activity_title);
62         WebkitHelpers.appendWebViewVersionToTitle(this);
63 
64         final WebView webView = findViewById(R.id.tracing_controller_webview);
65         webView.setWebViewClient(new WebViewClient());
66 
67         final EditText navigationBar = findViewById(R.id.tracing_controller_edittext);
68 
69         final TextView infoView = findViewById(R.id.tracing_controller_textview);
70         infoView.setVisibility(View.GONE);
71 
72         final Button tracingButton = findViewById(R.id.tracing_controller_button);
73 
74         if (!WebViewFeature.isFeatureSupported(WebViewFeature.TRACING_CONTROLLER_BASIC_USAGE)) {
75             navigationBar.setVisibility(View.GONE);
76             webView.setVisibility(View.GONE);
77             tracingButton.setVisibility(View.GONE);
78             infoView.setVisibility(View.GONE);
79             WebkitHelpers.showMessageInActivity(this, R.string.webkit_api_not_available);
80             return;
81         }
82 
83         final TracingController tracingController = TracingController.getInstance();
84 
85         navigationBar.setOnEditorActionListener((TextView v, int actionId, KeyEvent event) -> {
86             if (actionId == EditorInfo.IME_ACTION_NEXT) {
87                 String url = navigationBar.getText().toString();
88                 if (!url.isEmpty()) {
89                     if (!url.startsWith("http")) url = "http://" + url;
90                     webView.loadUrl(url);
91                     navigationBar.setText("");
92                 }
93                 return true;
94             }
95             return false;
96         });
97 
98         tracingButton.setOnClickListener(v -> {
99             if (tracingController.isTracing()) {
100                 try {
101                     tracingButton.setEnabled(false);
102                     final String logPath = getExternalFilesDir(null) + File.separator + "tc.json";
103                     FileOutputStream os = new VerifyingFileOutputStream(logPath, infoView,
104                             tracingButton);
105                     ExecutorService executor = Executors.newSingleThreadExecutor();
106                     tracingController.stop(os, executor);
107                 } catch (FileNotFoundException e) {
108 
109                     e.printStackTrace();
110                 }
111             } else {
112                 TracingConfig config = new TracingConfig.Builder().addCategories(
113                         TracingConfig.CATEGORIES_ANDROID_WEBVIEW).build();
114                 tracingController.start(config);
115                 tracingButton.setText(R.string.tracing_controller_stop_tracing);
116             }
117         });
118 
119 
120     }
121 
122     private class VerifyingFileOutputStream extends FileOutputStream {
123 
124         private final String mLogPath;
125         private final TextView mInfoView;
126         private final Button mTracingButton;
127 
VerifyingFileOutputStream(String logPath, TextView infoView, Button tracingButton)128         VerifyingFileOutputStream(String logPath, TextView infoView, Button tracingButton)
129                 throws FileNotFoundException {
130             super(logPath);
131             mLogPath = logPath;
132             mInfoView = infoView;
133             mTracingButton = tracingButton;
134         }
135 
136         @Override
close()137         public void close() throws IOException {
138             super.close();
139             runOnUiThread(() -> {
140                 mInfoView.setVisibility(View.VISIBLE);
141                 mTracingButton.setVisibility(View.GONE);
142                 mInfoView.setText(getString(R.string.tracing_controller_log_path, mLogPath));
143                 try {
144                     verifyJSON(mLogPath);
145                 } catch (IOException | JSONException e) {
146                     mInfoView.setText(R.string.tracing_controller_invalid_log);
147                 }
148             });
149         }
150 
verifyJSON(String logPath)151         private void verifyJSON(String logPath) throws IOException, JSONException {
152             StringBuilder builder = new StringBuilder();
153             FileInputStream fis = new FileInputStream(logPath);
154             BufferedReader br = new BufferedReader(new InputStreamReader(fis, UTF_8));
155             String sCurrentLine;
156             while ((sCurrentLine = br.readLine()) != null) {
157                 builder.append(sCurrentLine).append("\n");
158             }
159 
160             // Throw exception if JSON is incorrect
161             new JSONObject(builder.toString());
162         }
163 
164     }
165 }
166