2 * Copyright © 2017-2020 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.asynctasks;
22 import android.app.Activity;
23 import android.content.Context;
24 import android.content.SharedPreferences;
25 import android.graphics.Typeface;
26 import android.os.AsyncTask;
27 import android.os.Build;
28 import android.os.LocaleList;
29 import android.preference.PreferenceManager;
30 import android.text.SpannableStringBuilder;
31 import android.text.Spanned;
32 import android.text.style.StyleSpan;
33 import android.view.View;
34 import android.webkit.CookieManager;
35 import android.widget.ProgressBar;
36 import android.widget.TextView;
38 import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
40 import com.stoutner.privacybrowser.R;
41 import com.stoutner.privacybrowser.helpers.ProxyHelper;
43 import java.io.BufferedInputStream;
44 import java.io.ByteArrayOutputStream;
45 import java.io.IOException;
46 import java.io.InputStream;
47 import java.lang.ref.WeakReference;
48 import java.net.HttpURLConnection;
49 import java.net.Proxy;
51 import java.util.Locale;
53 // This must run asynchronously because it involves a network request. `String` declares the parameters. `Void` does not declare progress units. `SpannableStringBuilder[]` contains the results.
54 public class GetSource extends AsyncTask<String, Void, SpannableStringBuilder[]> {
55 // Define weak references to the calling context and activity.
56 private WeakReference<Context> contextWeakReference;
57 private WeakReference<Activity> activityWeakReference;
59 // Store the user agent.
60 private String userAgent;
62 public GetSource(Context context, Activity activity, String userAgent) {
63 // Populate the weak references to the calling context and activity.
64 contextWeakReference = new WeakReference<>(context);
65 activityWeakReference = new WeakReference<>(activity);
67 // Store the user agent.
68 this.userAgent = userAgent;
71 // `onPreExecute()` operates on the UI thread.
73 protected void onPreExecute() {
74 // Get a handle for the calling activity.
75 Activity activity = activityWeakReference.get();
77 // Abort if the activity is gone.
78 if ((activity == null) || activity.isFinishing()) {
82 // Get a handle for the progress bar.
83 ProgressBar progressBar = activity.findViewById(R.id.progress_bar);
85 // Make the progress bar visible.
86 progressBar.setVisibility(View.VISIBLE);
88 // Set the progress bar to be indeterminate.
89 progressBar.setIndeterminate(true);
93 protected SpannableStringBuilder[] doInBackground(String... formattedUrlString) {
94 // Initialize the response body String.
95 SpannableStringBuilder requestHeadersBuilder = new SpannableStringBuilder();
96 SpannableStringBuilder responseMessageBuilder = new SpannableStringBuilder();
97 SpannableStringBuilder responseHeadersBuilder = new SpannableStringBuilder();
98 SpannableStringBuilder responseBodyBuilder = new SpannableStringBuilder();
100 // Get a handle for the context and activity.
101 Context context = contextWeakReference.get();
102 Activity activity = activityWeakReference.get();
104 // Abort if the activity is gone.
105 if ((activity == null) || activity.isFinishing()) {
106 return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder};
109 // Because everything relating to requesting data from a webserver can throw errors, the entire section must catch `IOExceptions`.
111 // Get the current URL from the main activity.
112 URL url = new URL(formattedUrlString[0]);
114 // Instantiate the proxy helper.
115 ProxyHelper proxyHelper = new ProxyHelper();
117 // Get the current proxy.
118 Proxy proxy = proxyHelper.getCurrentProxy(context);
120 // Open a connection to the URL. No data is actually sent at this point.
121 HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection(proxy);
123 // Define the variables necessary to build the request headers.
124 requestHeadersBuilder = new SpannableStringBuilder();
125 int oldRequestHeadersBuilderLength;
126 int newRequestHeadersBuilderLength;
129 // Set the `Host` header property.
130 httpUrlConnection.setRequestProperty("Host", url.getHost());
132 // Add the `Host` header to the string builder and format the text.
133 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
134 requestHeadersBuilder.append("Host", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
135 } else { // Older versions not so much.
136 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
137 requestHeadersBuilder.append("Host");
138 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
139 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
141 requestHeadersBuilder.append(": ");
142 requestHeadersBuilder.append(url.getHost());
145 // Set the `Connection` header property.
146 httpUrlConnection.setRequestProperty("Connection", "keep-alive");
148 // Add the `Connection` header to the string builder and format the text.
149 requestHeadersBuilder.append(System.getProperty("line.separator"));
150 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
151 requestHeadersBuilder.append("Connection", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
152 } else { // Older versions not so much.
153 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
154 requestHeadersBuilder.append("Connection");
155 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
156 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
158 requestHeadersBuilder.append(": keep-alive");
161 // Set the `Upgrade-Insecure-Requests` header property.
162 httpUrlConnection.setRequestProperty("Upgrade-Insecure-Requests", "1");
164 // Add the `Upgrade-Insecure-Requests` header to the string builder and format the text.
165 requestHeadersBuilder.append(System.getProperty("line.separator"));
166 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
167 requestHeadersBuilder.append("Upgrade-Insecure-Requests", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
168 } else { // Older versions not so much.
169 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
170 requestHeadersBuilder.append("Upgrade-Insecure_Requests");
171 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
172 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
174 requestHeadersBuilder.append(": 1");
177 // Set the `User-Agent` header property.
178 httpUrlConnection.setRequestProperty("User-Agent", userAgent);
180 // Add the `User-Agent` header to the string builder and format the text.
181 requestHeadersBuilder.append(System.getProperty("line.separator"));
182 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
183 requestHeadersBuilder.append("User-Agent", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
184 } else { // Older versions not so much.
185 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
186 requestHeadersBuilder.append("User-Agent");
187 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
188 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
190 requestHeadersBuilder.append(": ");
191 requestHeadersBuilder.append(userAgent);
194 // Set the `x-requested-with` header property.
195 httpUrlConnection.setRequestProperty("x-requested-with", "");
197 // Add the `x-requested-with` header to the string builder and format the text.
198 requestHeadersBuilder.append(System.getProperty("line.separator"));
199 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
200 requestHeadersBuilder.append("x-requested-with", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
201 } else { // Older versions not so much.
202 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
203 requestHeadersBuilder.append("x-requested-with");
204 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
205 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
207 requestHeadersBuilder.append(": ");
210 // Set the `Sec-Fetch-Site` header property.
211 httpUrlConnection.setRequestProperty("Sec-Fetch-Site", "none");
213 // Add the `Sec-Fetch-Site` header to the string builder and format the text.
214 requestHeadersBuilder.append(System.getProperty("line.separator"));
215 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
216 requestHeadersBuilder.append("Sec-Fetch-Site", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
217 } else { // Older versions not so much.
218 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
219 requestHeadersBuilder.append("Sec-Fetch-Site");
220 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
221 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
223 requestHeadersBuilder.append(": none");
226 // Set the `Sec-Fetch-Mode` header property.
227 httpUrlConnection.setRequestProperty("Sec-Fetch-Mode", "navigate");
229 // Add the `Sec-Fetch-Mode` header to the string builder and format the text.
230 requestHeadersBuilder.append(System.getProperty("line.separator"));
231 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
232 requestHeadersBuilder.append("Sec-Fetch-Mode", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
233 } else { // Older versions not so much.
234 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
235 requestHeadersBuilder.append("Sec-Fetch-Mode");
236 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
237 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
239 requestHeadersBuilder.append(": navigate");
242 // Set the `Sec-Fetch-User` header property.
243 httpUrlConnection.setRequestProperty("Sec-Fetch-User", "?1");
245 // Add the `Sec-Fetch-User` header to the string builder and format the text.
246 requestHeadersBuilder.append(System.getProperty("line.separator"));
247 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
248 requestHeadersBuilder.append("Sec-Fetch-User", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
249 } else { // Older versions not so much.
250 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
251 requestHeadersBuilder.append("Sec-Fetch-User");
252 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
253 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
255 requestHeadersBuilder.append(": ?1");
258 // Get a handle for the shared preferences.
259 SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext());
261 // Only populate `Do Not Track` if it is enabled.
262 if (sharedPreferences.getBoolean("do_not_track", false)) {
263 // Set the `dnt` header property.
264 httpUrlConnection.setRequestProperty("dnt", "1");
266 // Add the `dnt` header to the string builder and format the text.
267 requestHeadersBuilder.append(System.getProperty("line.separator"));
268 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
269 requestHeadersBuilder.append("dnt", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
270 } else { // Older versions not so much.
271 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
272 requestHeadersBuilder.append("dnt");
273 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
274 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
276 requestHeadersBuilder.append(": 1");
280 // Set the `Accept` header property.
281 httpUrlConnection.setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3");
283 // Add the `Accept` header to the string builder and format the text.
284 requestHeadersBuilder.append(System.getProperty("line.separator"));
285 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
286 requestHeadersBuilder.append("Accept", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
287 } else { // Older versions not so much.
288 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
289 requestHeadersBuilder.append("Accept");
290 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
291 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
293 requestHeadersBuilder.append(": ");
294 requestHeadersBuilder.append("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3");
297 // Instantiate a locale string.
300 // Populate the locale string.
301 if (Build.VERSION.SDK_INT >= 24) { // SDK >= 24 has a list of locales.
302 // Get the list of locales.
303 LocaleList localeList = activity.getResources().getConfiguration().getLocales();
305 // Initialize a string builder to extract the locales from the list.
306 StringBuilder localesStringBuilder = new StringBuilder();
308 // Initialize a `q` value, which is used by `WebView` to indicate the order of importance of the languages.
311 // Populate the string builder with the contents of the locales list.
312 for (int i = 0; i < localeList.size(); i++) {
313 // Append a comma if there is already an item in the string builder.
315 localesStringBuilder.append(",");
318 // Get the locale from the list.
319 Locale locale = localeList.get(i);
321 // Add the locale to the string. `locale` by default displays as `en_US`, but WebView uses the `en-US` format.
322 localesStringBuilder.append(locale.getLanguage());
323 localesStringBuilder.append("-");
324 localesStringBuilder.append(locale.getCountry());
326 // If not the first locale, append `;q=0.x`, which drops by .1 for each removal from the main locale until q=0.1.
328 localesStringBuilder.append(";q=0.");
329 localesStringBuilder.append(q);
332 // Decrement `q` if it is greater than 1.
337 // Add a second entry for the language only portion of the locale.
338 localesStringBuilder.append(",");
339 localesStringBuilder.append(locale.getLanguage());
341 // Append `1;q=0.x`, which drops by .1 for each removal form the main locale until q=0.1.
342 localesStringBuilder.append(";q=0.");
343 localesStringBuilder.append(q);
345 // Decrement `q` if it is greater than 1.
351 // Store the populated string builder in the locale string.
352 localeString = localesStringBuilder.toString();
353 } else { // SDK < 24 only has a primary locale.
354 // Store the locale in the locale string.
355 localeString = Locale.getDefault().toString();
358 // Set the `Accept-Language` header property.
359 httpUrlConnection.setRequestProperty("Accept-Language", localeString);
361 // Add the `Accept-Language` header to the string builder and format the text.
362 requestHeadersBuilder.append(System.getProperty("line.separator"));
363 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
364 requestHeadersBuilder.append("Accept-Language", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
365 } else { // Older versions not so much.
366 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
367 requestHeadersBuilder.append("Accept-Language");
368 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
369 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
371 requestHeadersBuilder.append(": ");
372 requestHeadersBuilder.append(localeString);
375 // Get the cookies for the current domain.
376 String cookiesString = CookieManager.getInstance().getCookie(url.toString());
378 // Only process the cookies if they are not null.
379 if (cookiesString != null) {
380 // Add the cookies to the header property.
381 httpUrlConnection.setRequestProperty("Cookie", cookiesString);
383 // Add the cookie header to the string builder and format the text.
384 requestHeadersBuilder.append(System.getProperty("line.separator"));
385 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
386 requestHeadersBuilder.append("Cookie", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
387 } else { // Older versions not so much.
388 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
389 requestHeadersBuilder.append("Cookie");
390 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
391 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
393 requestHeadersBuilder.append(": ");
394 requestHeadersBuilder.append(cookiesString);
398 // `HttpUrlConnection` sets `Accept-Encoding` to be `gzip` by default. If the property is manually set, than `HttpUrlConnection` does not process the decoding.
399 // Add the `Accept-Encoding` header to the string builder and format the text.
400 requestHeadersBuilder.append(System.getProperty("line.separator"));
401 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
402 requestHeadersBuilder.append("Accept-Encoding", new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
403 } else { // Older versions not so much.
404 oldRequestHeadersBuilderLength = requestHeadersBuilder.length();
405 requestHeadersBuilder.append("Accept-Encoding");
406 newRequestHeadersBuilderLength = requestHeadersBuilder.length();
407 requestHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldRequestHeadersBuilderLength, newRequestHeadersBuilderLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
409 requestHeadersBuilder.append(": gzip");
412 // 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.
414 // Initialize the string builders.
415 responseMessageBuilder = new SpannableStringBuilder();
416 responseHeadersBuilder = new SpannableStringBuilder();
418 // Get the response code, which causes the connection to the server to be made.
419 int responseCode = httpUrlConnection.getResponseCode();
421 // Populate the response message string builder.
422 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
423 responseMessageBuilder.append(String.valueOf(responseCode), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
424 } else { // Older versions not so much.
425 responseMessageBuilder.append(String.valueOf(responseCode));
426 int newLength = responseMessageBuilder.length();
427 responseMessageBuilder.setSpan(new StyleSpan(Typeface.BOLD), 0, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
429 responseMessageBuilder.append(": ");
430 responseMessageBuilder.append(httpUrlConnection.getResponseMessage());
432 // Initialize the iteration variable.
435 // Iterate through the received header fields.
436 while (httpUrlConnection.getHeaderField(i) != null) {
437 // Add a new line if there is already information in the string builder.
439 responseHeadersBuilder.append(System.getProperty("line.separator"));
442 // Add the header to the string builder and format the text.
443 if (Build.VERSION.SDK_INT >= 21) { // Newer versions of Android are so smart.
444 responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i), new StyleSpan(Typeface.BOLD), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
445 } else { // Older versions not so much.
446 int oldLength = responseHeadersBuilder.length();
447 responseHeadersBuilder.append(httpUrlConnection.getHeaderFieldKey(i));
448 int newLength = responseHeadersBuilder.length();
449 responseHeadersBuilder.setSpan(new StyleSpan(Typeface.BOLD), oldLength, newLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
451 responseHeadersBuilder.append(": ");
452 responseHeadersBuilder.append(httpUrlConnection.getHeaderField(i));
454 // Increment the iteration variable.
458 // Instantiate an input stream for the response body.
459 InputStream inputStream;
461 // Get the correct input stream based on the response code.
462 if (responseCode == 404) { // Get the error stream.
463 inputStream = new BufferedInputStream(httpUrlConnection.getErrorStream());
464 } else { // Get the response body stream.
465 inputStream = new BufferedInputStream(httpUrlConnection.getInputStream());
468 // Initialize the byte array output stream and the conversion buffer byte array.
469 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
470 byte[] conversionBufferByteArray = new byte[1024];
472 // Define the buffer length variable.
476 // Attempt to read data from the input stream and store it in the conversion buffer byte array. Also store the amount of data read in the buffer length variable.
477 while ((bufferLength = inputStream.read(conversionBufferByteArray)) > 0) { // Proceed while the amount of data stored in the buffer is > 0.
478 // Write the contents of the conversion buffer to the byte array output stream.
479 byteArrayOutputStream.write(conversionBufferByteArray, 0, bufferLength);
481 } catch (IOException exception) {
485 // Close the input stream.
488 // Populate the response body string with the contents of the byte array output stream.
489 responseBodyBuilder.append(byteArrayOutputStream.toString());
491 // Disconnect HTTP URL connection.
492 httpUrlConnection.disconnect();
494 } catch (IOException exception) {
495 exception.printStackTrace();
498 // Return the response body string as the result.
499 return new SpannableStringBuilder[] {requestHeadersBuilder, responseMessageBuilder, responseHeadersBuilder, responseBodyBuilder};
502 // `onPostExecute()` operates on the UI thread.
504 protected void onPostExecute(SpannableStringBuilder[] viewSourceStringArray){
505 // Get a handle for the activity.
506 Activity activity = activityWeakReference.get();
508 // Abort if the activity is gone.
509 if ((activity == null) || activity.isFinishing()) {
513 // Get handles for the text views.
514 TextView requestHeadersTextView = activity.findViewById(R.id.request_headers);
515 TextView responseMessageTextView = activity.findViewById(R.id.response_message);
516 TextView responseHeadersTextView = activity.findViewById(R.id.response_headers);
517 TextView responseBodyTextView = activity.findViewById(R.id.response_body);
518 ProgressBar progressBar = activity.findViewById(R.id.progress_bar);
519 SwipeRefreshLayout swipeRefreshLayout = activity.findViewById(R.id.view_source_swiperefreshlayout);
521 // Populate the text views. This can take a long time, and freeze the user interface, if the response body is particularly large.
522 requestHeadersTextView.setText(viewSourceStringArray[0]);
523 responseMessageTextView.setText(viewSourceStringArray[1]);
524 responseHeadersTextView.setText(viewSourceStringArray[2]);
525 responseBodyTextView.setText(viewSourceStringArray[3]);
527 // Hide the progress bar.
528 progressBar.setIndeterminate(false);
529 progressBar.setVisibility(View.GONE);
531 //Stop the swipe to refresh indicator if it is running
532 swipeRefreshLayout.setRefreshing(false);