diff options
Diffstat (limited to 'samples/browseable/NetworkConnect/src/com.example.android.networkconnect/NetworkFragment.java')
-rw-r--r-- | samples/browseable/NetworkConnect/src/com.example.android.networkconnect/NetworkFragment.java | 289 |
1 files changed, 289 insertions, 0 deletions
diff --git a/samples/browseable/NetworkConnect/src/com.example.android.networkconnect/NetworkFragment.java b/samples/browseable/NetworkConnect/src/com.example.android.networkconnect/NetworkFragment.java new file mode 100644 index 000000000..cc41075a1 --- /dev/null +++ b/samples/browseable/NetworkConnect/src/com.example.android.networkconnect/NetworkFragment.java @@ -0,0 +1,289 @@ +/* + * 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; + } + } +} |