2 * Copyright © 2017-2018 Soren Stoutner <soren@stoutner.com>.
4 * This file is part of Privacy Browser <https://www.stoutner.com/privacy-browser>.
6 * Privacy Browser is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * Privacy Browser is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with Privacy Browser. If not, see <http://www.gnu.org/licenses/>.
20 package com.stoutner.privacybrowser.activities;
22 import android.annotation.SuppressLint;
23 import android.app.Activity;
24 import android.app.DialogFragment;
25 import android.content.Context;
26 import android.content.SharedPreferences;
27 import android.graphics.Typeface;
28 import android.os.AsyncTask;
29 import android.os.Build;
30 import android.os.Bundle;
31 import android.os.LocaleList;
32 import android.preference.PreferenceManager;
33 import android.support.v4.app.NavUtils;
34 import android.support.v7.app.ActionBar;
35 import android.support.v7.app.AppCompatActivity;
36 import android.support.v7.widget.Toolbar;
37 import android.text.SpannableStringBuilder;
38 import android.text.Spanned;
39 import android.text.style.ForegroundColorSpan;
40 import android.text.style.StyleSpan;
41 import android.view.KeyEvent;
42 import android.view.Menu;
43 import android.view.MenuItem;
44 import android.view.View;
45 import android.view.inputmethod.InputMethodManager;
46 import android.webkit.CookieManager;
47 import android.widget.EditText;
48 import android.widget.ProgressBar;
49 import android.widget.TextView;
51 import com.stoutner.privacybrowser.R;
52 import com.stoutner.privacybrowser.dialogs.AboutViewSourceDialog;
54 import java.io.BufferedInputStream;
55 import java.io.ByteArrayOutputStream;
56 import java.io.IOException;
57 import java.io.InputStream;
58 import java.net.HttpURLConnection;
60 import java.util.Locale;
62 public class ViewSourceActivity extends AppCompatActivity {
63 // `activity` is used in `onCreate()` and `goBack()`.
66 // The color spans are used in `onCreate()` and `highlightUrlText()`.
67 private ForegroundColorSpan redColorSpan;
68 private ForegroundColorSpan initialGrayColorSpan;
69 private ForegroundColorSpan finalGrayColorSpan;
72 protected void onCreate(Bundle savedInstanceState) {
74 if (MainWebViewActivity.darkTheme) {
75 setTheme(R.style.PrivacyBrowserDark);
77 setTheme(R.style.PrivacyBrowserLight);
80 // Run the default commands.
81 super.onCreate(savedInstanceState);
83 // Store a handle for the current activity.
86 // Set the content view.
87 setContentView(R.layout.view_source_coordinatorlayout);
89 // `SupportActionBar` from `android.support.v7.app.ActionBar` must be used until the minimum API is >= 21.
90 Toolbar viewSourceAppBar = findViewById(R.id.view_source_toolbar);
91 setSupportActionBar(viewSourceAppBar);
94 final ActionBar appBar = getSupportActionBar();
96 // Remove the incorrect warning in Android Studio that appBar might be null.
97 assert appBar != null;
99 // Add the custom layout to the app bar.
100 appBar.setCustomView(R.layout.view_source_app_bar);
101 appBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
103 // Get a handle for the url text box.
104 EditText urlEditText = findViewById(R.id.url_edittext);
106 // Get the formatted URL string from the main activity.
107 String formattedUrlString = MainWebViewActivity.formattedUrlString;
109 // Populate the URL text box.
110 urlEditText.setText(formattedUrlString);
112 // Initialize the foreground color spans for highlighting the URLs. We have to use the deprecated `getColor()` until API >= 23.
113 redColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.red_a700));
114 initialGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
115 finalGrayColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.gray_500));
117 // Apply text highlighting to the URL.
120 // Get a handle for the input method manager, which is used to hide the keyboard.
121 InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
123 // Let Android Studio know that we aren't worried about the input method manager being null.
124 assert inputMethodManager != null;
126 // Remove the formatting from the URL when the user is editing the text.
127 urlEditText.setOnFocusChangeListener((View v, boolean hasFocus) -> {
128 if (hasFocus) { // The user is editing `urlTextBox`.
129 // Remove the highlighting.
130 urlEditText.getText().removeSpan(redColorSpan);
131 urlEditText.getText().removeSpan(initialGrayColorSpan);
132 urlEditText.getText().removeSpan(finalGrayColorSpan);
133 } else { // The user has stopped editing `urlTextBox`.
134 // Hide the soft keyboard.
135 inputMethodManager.hideSoftInputFromWindow(urlEditText.getWindowToken(), 0);
137 // Reapply the highlighting.
144 // Set the go button on the keyboard to request new source data.
145 urlEditText.setOnKeyListener((View v, int keyCode, KeyEvent event) -> {
146 // Request new source data if the enter key was pressed.
147 if ((event.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_ENTER)) {
148 // Hide the soft keyboard.
149 inputMethodManager.hideSoftInputFromWindow(urlEditText.getWindowToken(), 0);
151 // Remove the focus from the URL box.
152 urlEditText.clearFocus();
154 // Get new source data for the current URL.
155 new GetSource().execute(urlEditText.getText().toString());
157 // Consume the key press.
160 // Do not consume the key press.
165 // Get the source as an `AsyncTask`.
166 new GetSource().execute(formattedUrlString);
170 public boolean onCreateOptionsMenu(Menu menu) {
171 // Inflate the menu; this adds items to the action bar if it is present.
172 getMenuInflater().inflate(R.menu.view_source_options_menu, menu);
179 public boolean onOptionsItemSelected(MenuItem menuItem) {
180 // Get a handle for the about alert dialog.
181 DialogFragment aboutDialogFragment = new AboutViewSourceDialog();
183 // Show the about alert dialog.
184 aboutDialogFragment.show(getFragmentManager(), getString(R.string.about));
186 // Consume the event.
190 public void goBack(View view) {
192 NavUtils.navigateUpFromSameTask(activity);
195 private void highlightUrlText() {
196 // Get a handle for the URL EditText.
197 EditText urlEditText = findViewById(R.id.url_edittext);
200 String urlString = urlEditText.getText().toString();
202 // Highlight the beginning of the URL.
203 if (urlString.startsWith("http://")) { // Highlight the protocol of connections that are not encrypted.
204 urlEditText.getText().setSpan(redColorSpan, 0, 7, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
205 } else if (urlString.startsWith("https://")) { // De-emphasize the protocol of connections that are encrypted.
206 urlEditText.getText().setSpan(initialGrayColorSpan, 0, 8, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
209 // Get the index of the `/` immediately after the domain name.
210 int endOfDomainName = urlString.indexOf("/", (urlString.indexOf("//") + 2));
212 // De-emphasize the text after the domain name.
213 if (endOfDomainName > 0) {
214 urlEditText.getText().setSpan(finalGrayColorSpan, endOfDomainName, urlString.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
218 // The first `String` declares the parameters. The `Void` does not declare progress units. The last `String` contains the results.
219 // `StaticFieldLeaks` are suppressed so that Android Studio doesn't complain about running an AsyncTask in a non-static context.
220 @SuppressLint("StaticFieldLeak")
221 private class GetSource extends AsyncTask<String, Void, String> {
222 // The class variables pass information from `doInBackground()` to `onPostExecute()`.
223 SpannableStringBuilder responseMessageBuilder;
224 SpannableStringBuilder requestHeadersBuilder;
225 SpannableStringBuilder responseHeadersBuilder;
227 // `onPreExecute()` operates on the UI thread.
229 protected void onPreExecute() {
230 // Get a handle for the progress bar.
231 ProgressBar progressBar = findViewById(R.id.progress_bar);
233 // Make the progress bar visible.
234 progressBar.setVisibility(View.VISIBLE);
236 // Set the progress bar to be indeterminate.
237 progressBar.setIndeterminate(true);
241 protected String doInBackground(String... formattedUrlString) {
242 // Initialize the response body `String`.
243 String responseBodyString = "";
245 // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch `IOExceptions`.
247 // Get the current URL from the main activity.
248 URL url = new URL(formattedUrlString[0]);
250 // Open a connection to the URL. No data is actually sent at this point.
251 HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection();
253 // Instantiate the variables necessary to build the request headers.
254 requestHeadersBuilder = new SpannableStringBuilder();
255 int oldRequestHeadersBuilderLength;
256 int newRequestHeadersBuilderLength;
259 // Set the `Host` header property.
260 httpUrlConnection.setRequestProperty("Host", url.getHost());
262 // Add the `Host` header to the string builder and format the text.
263 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
264 requestHeadersBuilder.append("Host", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
265 } else { // Older versions not so much.
266 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
267 requestHeadersBuilder.append("Host");
268 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
269 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
271 requestHeadersBuilder.append(": ");
272 requestHeadersBuilder.append(url.getHost());
275 // Set the `Connection` header property.
276 httpUrlConnection.setRequestProperty("Connection", "keep-alive");
278 // Add the `Connection` header to the string builder and format the text.
279 requestHeadersBuilder.append(System.getProperty("line.separator"));
280 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
281 requestHeadersBuilder.append("Connection", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
282 } else { // Older versions not so much.
283 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
284 requestHeadersBuilder.append("Connection");
285 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
286 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
288 requestHeadersBuilder.append(": keep-alive");
291 // Get the current `User-Agent` string.
292 String userAgentString = MainWebViewActivity.appliedUserAgentString;
294 // Set the `User-Agent` header property.
295 httpUrlConnection.setRequestProperty("User-Agent", userAgentString);
297 // Add the `User-Agent` header to the string builder and format the text.
298 requestHeadersBuilder.append(System.getProperty("line.separator"));
299 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
300 requestHeadersBuilder.append("User-Agent", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
301 } else { // Older versions not so much.
302 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
303 requestHeadersBuilder.append("User-Agent");
304 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
305 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
307 requestHeadersBuilder.append(": ");
308 requestHeadersBuilder.append(userAgentString);
311 // Set the `Upgrade-Insecure-Requests` header property.
312 httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1");
314 // Add the `Upgrade-Insecure-Requests` header to the string builder and format the text.
315 requestHeadersBuilder.append(System.getProperty("line.separator"));
316 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
317 requestHeadersBuilder.append("Upgrade-Insecure-Requests", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
318 } else { // Older versions not so much.
319 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
320 requestHeadersBuilder.append("Upgrade-Insecure_Requests");
321 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
322 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
324 requestHeadersBuilder.append(": 1");
327 // Set the `x-requested-with` header property.
328 httpUrlConnection.setRequestProperty("x-requested-with", "");
330 // Add the `x-requested-with` header to the string builder and format the text.
331 requestHeadersBuilder.append(System.getProperty("line.separator"));
332 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
333 requestHeadersBuilder.append("x-requested-with", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
334 } else { // Older versions not so much.
335 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
336 requestHeadersBuilder.append("x-requested-with");
337 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
338 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
340 requestHeadersBuilder.append(": ");
343 // Get a handle for the shared preferences.
344 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
346 // Only populate `Do Not Track` if it is enabled.
347 if (sharedPreferences.getBoolean("do_not_track", false)) {
348 // Set the `dnt` header property.
349 httpUrlConnection.setRequestProperty("dnt", "1");
351 // Add the `dnt` header to the string builder and format the text.
352 requestHeadersBuilder.append(System.getProperty("line.separator"));
353 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
354 requestHeadersBuilder.append("dnt", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
355 } else { // Older versions not so much.
356 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
357 requestHeadersBuilder.append("dnt");
358 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
359 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
361 requestHeadersBuilder.append(": 1");
365 // Set the `Accept` header property.
366 httpUrlConnection.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
368 // Add the `Accept` header to the string builder and format the text.
369 requestHeadersBuilder.append(System.getProperty("line.separator"));
370 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
371 requestHeadersBuilder.append("Accept", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
372 } else { // Older versions not so much.
373 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
374 requestHeadersBuilder.append("Accept");
375 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
376 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
378 requestHeadersBuilder.append(": ");
379 requestHeadersBuilder.append("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
382 // Instantiate a locale string.
385 // Populate the locale string.
386 if (Build.VERSION.SDK_INT >= 24) { // SDK >= 24 has a list of locales.
387 // Get the list of locales.
388 LocaleList localeList = getResources().getConfiguration().getLocales();
390 // Initialize a string builder to extract the locales from the list.
391 StringBuilder localesStringBuilder = new StringBuilder();
393 // Initialize a `q` value, which is used by `WebView` to indicate the order of importance of the languages.
396 // Populate the string builder with the contents of the locales list.
397 for (int i = 0; i < localeList.size(); i++) {
398 // Append a comma if there is already an item in the string builder.
400 localesStringBuilder.append(",");
403 // Get the indicated locale from the list.
404 localesStringBuilder.append(localeList.get(i));
406 // If not the first locale, append `;q=0.i`, which drops by .1 for each removal from the main locale.
408 localesStringBuilder.append(";q=0.");
409 localesStringBuilder.append(q);
416 // Store the populated string builder in the locale string.
417 localeString = localesStringBuilder.toString();
418 } else { // SDK < 24 only has a primary locale.
419 // Store the locale in the locale string.
420 localeString = Locale.getDefault().toString();
423 // Set the `Accept-Language` header property.
424 httpUrlConnection.setRequestProperty("Accept-Language", localeString);
426 // Add the `Accept-Language` header to the string builder and format the text.
427 requestHeadersBuilder.append(System.getProperty("line.separator"));
428 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
429 requestHeadersBuilder.append("Accept-Language", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
430 } else { // Older versions not so much.
431 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
432 requestHeadersBuilder.append("Accept-Language");
433 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
434 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
436 requestHeadersBuilder.append(": ");
437 requestHeadersBuilder.append(localeString);
440 // Get the cookies for the current domain.
441 String cookiesString = CookieManager.getInstance().getCookie(url.toString());
443 // Only process the cookies if they are not null.
444 if (cookiesString != null) {
445 // Set the `Cookie` header property.
446 httpUrlConnection.setRequestProperty("Cookie", cookiesString);
448 // Add the `Cookie` header to the string builder and format the text.
449 requestHeadersBuilder.append(System.getProperty("line.separator"));
450 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
451 requestHeadersBuilder.append("Cookie", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
452 } else { // Older versions not so much.
453 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
454 requestHeadersBuilder.append("Cookie");
455 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
456 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
458 requestHeadersBuilder.append(": ");
459 requestHeadersBuilder.append(cookiesString);
463 // `HttpUrlConnection` sets `Accept-Encoding` to be `gzip` by default. If the property is manually set, than `HttpUrlConnection` does not process the decoding.
464 // Add the `Accept-Encoding` header to the string builder and format the text.
465 requestHeadersBuilder.append(System.getProperty("line.separator"));
466 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
467 requestHeadersBuilder.append("Accept-Encoding", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
468 } else { // Older versions not so much.
469 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
470 requestHeadersBuilder.append("Accept-Encoding");
471 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
472 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength + 1, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
474 requestHeadersBuilder.append(": gzip");
477 // The actual network request is in a `try` bracket so that `disconnect()` is run in the `finally` section even if an error is encountered in the main block.
479 // Initialize the string builders.
480 responseMessageBuilder = new SpannableStringBuilder();
481 responseHeadersBuilder = new SpannableStringBuilder();
483 // Get the response code, which causes the connection to the server to be made.
484 int responseCode = httpUrlConnection.getResponseCode();
486 // Populate the response message string builder.
487 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
488 responseMessageBuilder.append(String.valueOf(responseCode), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
489 } else { // Older versions not so much.
490 responseMessageBuilder.append(String.valueOf(responseCode));
491 int newLength = responseMessageBuilder.length();
492 responseMessageBuilder.setSpan(new StyleSpan(Typeface.BOLD), 0, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
494 responseMessageBuilder.append(": ");
495 responseMessageBuilder.append(httpUrlConnection.getResponseMessage());
497 // Initialize the iteration variable.
500 // Iterate through the received header fields.
501 while (httpUrlConnection.getHeaderField(i) != null) {
502 // Add a new line if there is already information in the string builder.
504 responseHeadersBuilder.append(System.getProperty("line.separator"));
507 // Add the header to the string builder and format the text.
508 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
509 responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
510 } else { // Older versions not so much.
511 int oldLength = responseHeadersBuilder.length();
512 responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i));
513 int newLength = responseHeadersBuilder.length();
514 responseHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldLength + 1, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
516 responseHeadersBuilder.append(": ");
517 responseHeadersBuilder.append(httpUrlConnection.getHeaderField(i));
519 // Increment the iteration variable.
523 // Instantiate an input stream for the response body.
524 InputStream inputStream;
526 // Get the correct input stream based on the response code.
527 if (responseCode == 404) { // Get the error stream.
528 inputStream = new BufferedInputStream(httpUrlConnection.getErrorStream());
529 } else { // Get the response body stream.
530 inputStream = new BufferedInputStream(httpUrlConnection.getInputStream());
533 // Initialize the byte array output stream and the conversion buffer byte array.
534 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
535 byte[] conversionBufferByteArray = new byte[1024];
537 // Instantiate the variable to track the buffer length.
541 // Attempt to read data from the input stream and store it in the conversion buffer byte array. Also store the amount of data transferred in the buffer length variable.
542 while ((bufferLength = inputStream.read(conversionBufferByteArray)) > 0) { // Proceed while the amount of data stored in the buffer is > 0.
543 // Write the contents of the conversion buffer to the byte array output stream.
544 byteArrayOutputStream.write(conversionBufferByteArray, 0, bufferLength);
546 } catch (IOException e) {
550 // Close the input stream.
553 // Populate the response body string with the contents of the byte array output stream.
554 responseBodyString = byteArrayOutputStream.toString();
556 // Disconnect `httpUrlConnection`.
557 httpUrlConnection.disconnect();
559 } catch (IOException e) {
563 // Return the response body string as the result.
564 return responseBodyString;
567 // `onPostExecute()` operates on the UI thread.
569 protected void onPostExecute(String responseBodyString){
570 // Get handles for the text views.
571 TextView requestHeadersTextView = findViewById(R.id.request_headers);
572 TextView responseMessageTextView = findViewById(R.id.response_message);
573 TextView responseHeadersTextView = findViewById(R.id.response_headers);
574 TextView responseBodyTextView = findViewById(R.id.response_body);
575 ProgressBar progressBar = findViewById(R.id.progress_bar);
577 // Populate the text views.
578 requestHeadersTextView.setText(requestHeadersBuilder);
579 responseMessageTextView.setText(responseMessageBuilder);
580 responseHeadersTextView.setText(responseHeadersBuilder);
581 responseBodyTextView.setText(responseBodyString);
583 // Hide the progress bar.
584 progressBar.setIndeterminate(false);
585 progressBar.setVisibility(View.GONE);