| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.example.android.networkconnect; |
| |
| import android.content.Context; |
| import android.net.ConnectivityManager; |
| import android.net.NetworkInfo; |
| import android.os.AsyncTask; |
| import android.os.Bundle; |
| import android.support.annotation.Nullable; |
| import android.support.v4.app.Fragment; |
| import android.support.v4.app.FragmentManager; |
| import android.util.Log; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.Reader; |
| import java.net.URL; |
| |
| import javax.net.ssl.HttpsURLConnection; |
| |
| /** |
| * Implementation of headless Fragment that runs an AsyncTask to fetch data from the network. |
| */ |
| public class NetworkFragment extends Fragment { |
| public static final String TAG = "NetworkFragment"; |
| |
| private static final String URL_KEY = "UrlKey"; |
| |
| private DownloadCallback mCallback; |
| private DownloadTask mDownloadTask; |
| private String mUrlString; |
| |
| /** |
| * Static initializer for NetworkFragment that sets the URL of the host it will be downloading |
| * from. |
| */ |
| public static NetworkFragment getInstance(FragmentManager fragmentManager, String url) { |
| // Recover NetworkFragment in case we are re-creating the Activity due to a config change. |
| // This is necessary because NetworkFragment might have a task that began running before |
| // the config change and has not finished yet. |
| // The NetworkFragment is recoverable via this method because it calls |
| // setRetainInstance(true) upon creation. |
| NetworkFragment networkFragment = (NetworkFragment) fragmentManager |
| .findFragmentByTag(NetworkFragment.TAG); |
| if (networkFragment == null) { |
| networkFragment = new NetworkFragment(); |
| Bundle args = new Bundle(); |
| args.putString(URL_KEY, url); |
| networkFragment.setArguments(args); |
| fragmentManager.beginTransaction().add(networkFragment, TAG).commit(); |
| } |
| return networkFragment; |
| } |
| |
| @Override |
| public void onCreate(@Nullable Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| // Retain this Fragment across configuration changes in the host Activity. |
| setRetainInstance(true); |
| mUrlString = getArguments().getString(URL_KEY); |
| } |
| |
| @Override |
| public void onAttach(Context context) { |
| super.onAttach(context); |
| // Host Activity will handle callbacks from task. |
| mCallback = (DownloadCallback)context; |
| } |
| |
| @Override |
| public void onDetach() { |
| super.onDetach(); |
| // Clear reference to host Activity. |
| mCallback = null; |
| } |
| |
| @Override |
| public void onDestroy() { |
| // Cancel task when Fragment is destroyed. |
| cancelDownload(); |
| super.onDestroy(); |
| } |
| |
| /** |
| * Start non-blocking execution of DownloadTask. |
| */ |
| public void startDownload() { |
| cancelDownload(); |
| mDownloadTask = new DownloadTask(); |
| mDownloadTask.execute(mUrlString); |
| } |
| |
| /** |
| * Cancel (and interrupt if necessary) any ongoing DownloadTask execution. |
| */ |
| public void cancelDownload() { |
| if (mDownloadTask != null) { |
| mDownloadTask.cancel(true); |
| mDownloadTask = null; |
| } |
| } |
| |
| /** |
| * Implementation of AsyncTask that runs a network operation on a background thread. |
| */ |
| private class DownloadTask extends AsyncTask<String, Integer, DownloadTask.Result> { |
| |
| /** |
| * Wrapper class that serves as a union of a result value and an exception. When the |
| * download task has completed, either the result value or exception can be a non-null |
| * value. This allows you to pass exceptions to the UI thread that were thrown during |
| * doInBackground(). |
| */ |
| class Result { |
| public String mResultValue; |
| public Exception mException; |
| public Result(String resultValue) { |
| mResultValue = resultValue; |
| } |
| public Result(Exception exception) { |
| mException = exception; |
| } |
| } |
| |
| /** |
| * Cancel background network operation if we do not have network connectivity. |
| */ |
| @Override |
| protected void onPreExecute() { |
| if (mCallback != null) { |
| NetworkInfo networkInfo = mCallback.getActiveNetworkInfo(); |
| if (networkInfo == null || !networkInfo.isConnected() || |
| (networkInfo.getType() != ConnectivityManager.TYPE_WIFI |
| && networkInfo.getType() != ConnectivityManager.TYPE_MOBILE)) { |
| // If no connectivity, cancel task and update Callback with null data. |
| mCallback.updateFromDownload(null); |
| cancel(true); |
| } |
| } |
| } |
| |
| /** |
| * Defines work to perform on the background thread. |
| */ |
| @Override |
| protected Result doInBackground(String... urls) { |
| Result result = null; |
| if (!isCancelled() && urls != null && urls.length > 0) { |
| String urlString = urls[0]; |
| try { |
| URL url = new URL(urlString); |
| String resultString = downloadUrl(url); |
| if (resultString != null) { |
| result = new Result(resultString); |
| } else { |
| throw new IOException("No response received."); |
| } |
| } catch(Exception e) { |
| result = new Result(e); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Send DownloadCallback a progress update. |
| */ |
| @Override |
| protected void onProgressUpdate(Integer... values) { |
| super.onProgressUpdate(values); |
| if (values.length >= 2) { |
| mCallback.onProgressUpdate(values[0], values[1]); |
| } |
| } |
| |
| /** |
| * Updates the DownloadCallback with the result. |
| */ |
| @Override |
| protected void onPostExecute(Result result) { |
| if (result != null && mCallback != null) { |
| if (result.mException != null) { |
| mCallback.updateFromDownload(result.mException.getMessage()); |
| } else if (result.mResultValue != null) { |
| mCallback.updateFromDownload(result.mResultValue); |
| } |
| mCallback.finishDownloading(); |
| } |
| } |
| |
| /** |
| * Override to add special behavior for cancelled AsyncTask. |
| */ |
| @Override |
| protected void onCancelled(Result result) { |
| } |
| |
| /** |
| * Given a URL, sets up a connection and gets the HTTP response body from the server. |
| * If the network request is successful, it returns the response body in String form. Otherwise, |
| * it will throw an IOException. |
| */ |
| private String downloadUrl(URL url) throws IOException { |
| InputStream stream = null; |
| HttpsURLConnection connection = null; |
| String result = null; |
| try { |
| connection = (HttpsURLConnection) url.openConnection(); |
| // Timeout for reading InputStream arbitrarily set to 3000ms. |
| connection.setReadTimeout(3000); |
| // Timeout for connection.connect() arbitrarily set to 3000ms. |
| connection.setConnectTimeout(3000); |
| // For this use case, set HTTP method to GET. |
| connection.setRequestMethod("GET"); |
| // Already true by default but setting just in case; needs to be true since this request |
| // is carrying an input (response) body. |
| connection.setDoInput(true); |
| // Open communications link (network traffic occurs here). |
| connection.connect(); |
| publishProgress(DownloadCallback.Progress.CONNECT_SUCCESS); |
| int responseCode = connection.getResponseCode(); |
| if (responseCode != HttpsURLConnection.HTTP_OK) { |
| throw new IOException("HTTP error code: " + responseCode); |
| } |
| // Retrieve the response body as an InputStream. |
| stream = connection.getInputStream(); |
| publishProgress(DownloadCallback.Progress.GET_INPUT_STREAM_SUCCESS, 0); |
| if (stream != null) { |
| // Converts Stream to String with max length of 500. |
| result = readStream(stream, 500); |
| publishProgress(DownloadCallback.Progress.PROCESS_INPUT_STREAM_SUCCESS, 0); |
| } |
| } finally { |
| // Close Stream and disconnect HTTPS connection. |
| if (stream != null) { |
| stream.close(); |
| } |
| if (connection != null) { |
| connection.disconnect(); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Converts the contents of an InputStream to a String. |
| */ |
| private String readStream(InputStream stream, int maxLength) throws IOException { |
| String result = null; |
| // Read InputStream using the UTF-8 charset. |
| InputStreamReader reader = new InputStreamReader(stream, "UTF-8"); |
| // Create temporary buffer to hold Stream data with specified max length. |
| char[] buffer = new char[maxLength]; |
| // Populate temporary buffer with Stream data. |
| int numChars = 0; |
| int readSize = 0; |
| while (numChars < maxLength && readSize != -1) { |
| numChars += readSize; |
| int pct = (100 * numChars) / maxLength; |
| publishProgress(DownloadCallback.Progress.PROCESS_INPUT_STREAM_IN_PROGRESS, pct); |
| readSize = reader.read(buffer, numChars, buffer.length - numChars); |
| } |
| if (numChars != -1) { |
| // The stream was not empty. |
| // Create String that is actual length of response body if actual length was less than |
| // max length. |
| numChars = Math.min(numChars, maxLength); |
| result = new String(buffer, 0, numChars); |
| } |
| return result; |
| } |
| } |
| } |