Skip to content

Most visited

Recently visited

navigation
NetworkConnect / src / com.example.android.networkconnect /

NetworkFragment.java

1
/*
2
 * Copyright (C) 2016 The Android Open Source Project
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
 
17
package com.example.android.networkconnect;
18
 
19
import android.content.Context;
20
import android.net.ConnectivityManager;
21
import android.net.NetworkInfo;
22
import android.os.AsyncTask;
23
import android.os.Bundle;
24
import android.support.annotation.Nullable;
25
import android.support.v4.app.Fragment;
26
import android.support.v4.app.FragmentManager;
27
import android.util.Log;
28
 
29
import java.io.IOException;
30
import java.io.InputStream;
31
import java.io.InputStreamReader;
32
import java.io.Reader;
33
import java.net.URL;
34
 
35
import javax.net.ssl.HttpsURLConnection;
36
 
37
/**
38
 * Implementation of headless Fragment that runs an AsyncTask to fetch data from the network.
39
 */
40
public class NetworkFragment extends Fragment {
41
    public static final String TAG = "NetworkFragment";
42
 
43
    private static final String URL_KEY = "UrlKey";
44
 
45
    private DownloadCallback mCallback;
46
    private DownloadTask mDownloadTask;
47
    private String mUrlString;
48
 
49
    /**
50
     * Static initializer for NetworkFragment that sets the URL of the host it will be downloading
51
     * from.
52
     */
53
    public static NetworkFragment getInstance(FragmentManager fragmentManager, String url) {
54
        // Recover NetworkFragment in case we are re-creating the Activity due to a config change.
55
        // This is necessary because NetworkFragment might have a task that began running before
56
        // the config change and has not finished yet.
57
        // The NetworkFragment is recoverable via this method because it calls
58
        // setRetainInstance(true) upon creation.
59
        NetworkFragment networkFragment = (NetworkFragment) fragmentManager
60
                .findFragmentByTag(NetworkFragment.TAG);
61
        if (networkFragment == null) {
62
            networkFragment = new NetworkFragment();
63
            Bundle args = new Bundle();
64
            args.putString(URL_KEY, url);
65
            networkFragment.setArguments(args);
66
            fragmentManager.beginTransaction().add(networkFragment, TAG).commit();
67
        }
68
        return networkFragment;
69
    }
70
 
71
    @Override
72
    public void onCreate(@Nullable Bundle savedInstanceState) {
73
        super.onCreate(savedInstanceState);
74
        // Retain this Fragment across configuration changes in the host Activity.
75
        setRetainInstance(true);
76
        mUrlString = getArguments().getString(URL_KEY);
77
    }
78
 
79
    @Override
80
    public void onAttach(Context context) {
81
        super.onAttach(context);
82
        // Host Activity will handle callbacks from task.
83
        mCallback = (DownloadCallback)context;
84
    }
85
 
86
    @Override
87
    public void onDetach() {
88
        super.onDetach();
89
        // Clear reference to host Activity.
90
        mCallback = null;
91
    }
92
 
93
    @Override
94
    public void onDestroy() {
95
        // Cancel task when Fragment is destroyed.
96
        cancelDownload();
97
        super.onDestroy();
98
    }
99
 
100
    /**
101
     * Start non-blocking execution of DownloadTask.
102
     */
103
    public void startDownload() {
104
        cancelDownload();
105
        mDownloadTask = new DownloadTask();
106
        mDownloadTask.execute(mUrlString);
107
    }
108
 
109
    /**
110
     * Cancel (and interrupt if necessary) any ongoing DownloadTask execution.
111
     */
112
    public void cancelDownload() {
113
        if (mDownloadTask != null) {
114
            mDownloadTask.cancel(true);
115
            mDownloadTask = null;
116
        }
117
    }
118
 
119
    /**
120
     * Implementation of AsyncTask that runs a network operation on a background thread.
121
     */
122
    private class DownloadTask extends AsyncTask<String, Integer, DownloadTask.Result> {
123
 
124
        /**
125
         * Wrapper class that serves as a union of a result value and an exception. When the
126
         * download task has completed, either the result value or exception can be a non-null
127
         * value. This allows you to pass exceptions to the UI thread that were thrown during
128
         * doInBackground().
129
         */
130
        class Result {
131
            public String mResultValue;
132
            public Exception mException;
133
            public Result(String resultValue) {
134
                mResultValue = resultValue;
135
            }
136
            public Result(Exception exception) {
137
                mException = exception;
138
            }
139
        }
140
 
141
        /**
142
         * Cancel background network operation if we do not have network connectivity.
143
         */
144
        @Override
145
        protected void onPreExecute() {
146
            if (mCallback != null) {
147
                NetworkInfo networkInfo = mCallback.getActiveNetworkInfo();
148
                if (networkInfo == null || !networkInfo.isConnected() ||
149
                        (networkInfo.getType() != ConnectivityManager.TYPE_WIFI
150
                                && networkInfo.getType() != ConnectivityManager.TYPE_MOBILE)) {
151
                    // If no connectivity, cancel task and update Callback with null data.
152
                    mCallback.updateFromDownload(null);
153
                    cancel(true);
154
                }
155
            }
156
        }
157
 
158
        /**
159
         * Defines work to perform on the background thread.
160
         */
161
        @Override
162
        protected Result doInBackground(String... urls) {
163
            Result result = null;
164
            if (!isCancelled() && urls != null && urls.length > 0) {
165
                String urlString = urls[0];
166
                try {
167
                    URL url = new URL(urlString);
168
                    String resultString = downloadUrl(url);
169
                    if (resultString != null) {
170
                        result = new Result(resultString);
171
                    } else {
172
                        throw new IOException("No response received.");
173
                    }
174
                } catch(Exception e) {
175
                    result = new Result(e);
176
                }
177
            }
178
            return result;
179
        }
180
 
181
        /**
182
         * Send DownloadCallback a progress update.
183
         */
184
        @Override
185
        protected void onProgressUpdate(Integer... values) {
186
            super.onProgressUpdate(values);
187
            if (values.length >= 2) {
188
                mCallback.onProgressUpdate(values[0], values[1]);
189
            }
190
        }
191
 
192
        /**
193
         * Updates the DownloadCallback with the result.
194
         */
195
        @Override
196
        protected void onPostExecute(Result result) {
197
            if (result != null && mCallback != null) {
198
                if (result.mException != null) {
199
                    mCallback.updateFromDownload(result.mException.getMessage());
200
                } else if (result.mResultValue != null) {
201
                    mCallback.updateFromDownload(result.mResultValue);
202
                }
203
                mCallback.finishDownloading();
204
            }
205
        }
206
 
207
        /**
208
         * Override to add special behavior for cancelled AsyncTask.
209
         */
210
        @Override
211
        protected void onCancelled(Result result) {
212
        }
213
 
214
        /**
215
         * Given a URL, sets up a connection and gets the HTTP response body from the server.
216
         * If the network request is successful, it returns the response body in String form. Otherwise,
217
         * it will throw an IOException.
218
         */
219
        private String downloadUrl(URL url) throws IOException {
220
            InputStream stream = null;
221
            HttpsURLConnection connection = null;
222
            String result = null;
223
            try {
224
                connection = (HttpsURLConnection) url.openConnection();
225
                // Timeout for reading InputStream arbitrarily set to 3000ms.
226
                connection.setReadTimeout(3000);
227
                // Timeout for connection.connect() arbitrarily set to 3000ms.
228
                connection.setConnectTimeout(3000);
229
                // For this use case, set HTTP method to GET.
230
                connection.setRequestMethod("GET");
231
                // Already true by default but setting just in case; needs to be true since this request
232
                // is carrying an input (response) body.
233
                connection.setDoInput(true);
234
                // Open communications link (network traffic occurs here).
235
                connection.connect();
236
                publishProgress(DownloadCallback.Progress.CONNECT_SUCCESS);
237
                int responseCode = connection.getResponseCode();
238
                if (responseCode != HttpsURLConnection.HTTP_OK) {
239
                    throw new IOException("HTTP error code: " + responseCode);
240
                }
241
                // Retrieve the response body as an InputStream.
242
                stream = connection.getInputStream();
243
                publishProgress(DownloadCallback.Progress.GET_INPUT_STREAM_SUCCESS, 0);
244
                if (stream != null) {
245
                    // Converts Stream to String with max length of 500.
246
                    result = readStream(stream, 500);
247
                    publishProgress(DownloadCallback.Progress.PROCESS_INPUT_STREAM_SUCCESS, 0);
248
                }
249
            } finally {
250
                // Close Stream and disconnect HTTPS connection.
251
                if (stream != null) {
252
                    stream.close();
253
                }
254
                if (connection != null) {
255
                    connection.disconnect();
256
                }
257
            }
258
            return result;
259
        }
260
 
261
        /**
262
         * Converts the contents of an InputStream to a String.
263
         */
264
        private String readStream(InputStream stream, int maxLength) throws IOException {
265
            String result = null;
266
            // Read InputStream using the UTF-8 charset.
267
            InputStreamReader reader = new InputStreamReader(stream, "UTF-8");
268
            // Create temporary buffer to hold Stream data with specified max length.
269
            char[] buffer = new char[maxLength];
270
            // Populate temporary buffer with Stream data.
271
            int numChars = 0;
272
            int readSize = 0;
273
            while (numChars < maxLength && readSize != -1) {
274
                numChars += readSize;
275
                int pct = (100 * numChars) / maxLength;
276
                publishProgress(DownloadCallback.Progress.PROCESS_INPUT_STREAM_IN_PROGRESS, pct);
277
                readSize = reader.read(buffer, numChars, buffer.length - numChars);
278
            }
279
            if (numChars != -1) {
280
                // The stream was not empty.
281
                // Create String that is actual length of response body if actual length was less than
282
                // max length.
283
                numChars = Math.min(numChars, maxLength);
284
                result = new String(buffer, 0, numChars);
285
            }
286
            return result;
287
        }
288
    }
289
}