content: /*
   * 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;
          }
      }
  }
  