1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 
15 package com.example.android.networkusage;
16 
17 import android.app.Activity;
18 import android.content.BroadcastReceiver;
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.IntentFilter;
22 import android.content.SharedPreferences;
23 import android.net.ConnectivityManager;
24 import android.net.NetworkInfo;
25 import android.os.AsyncTask;
26 import android.os.Bundle;
27 import android.preference.PreferenceManager;
28 import android.view.Menu;
29 import android.view.MenuInflater;
30 import android.view.MenuItem;
31 import android.webkit.WebView;
32 import android.widget.Toast;
33 
34 import com.example.android.networkusage.R;
35 import com.example.android.networkusage.StackOverflowXmlParser.Entry;
36 
37 import org.xmlpull.v1.XmlPullParserException;
38 
39 import java.io.IOException;
40 import java.io.InputStream;
41 import java.net.HttpURLConnection;
42 import java.net.URL;
43 import java.text.DateFormat;
44 import java.text.SimpleDateFormat;
45 import java.util.Calendar;
46 import java.util.List;
47 
48 
49 /**
50  * Main Activity for the sample application.
51  *
52  * This activity does the following:
53  *
54  * o Presents a WebView screen to users. This WebView has a list of HTML links to the latest
55  *   questions tagged 'android' on stackoverflow.com.
56  *
57  * o Parses the StackOverflow XML feed using XMLPullParser.
58  *
59  * o Uses AsyncTask to download and process the XML feed.
60  *
61  * o Monitors preferences and the device's network connection to determine whether
62  *   to refresh the WebView content.
63  */
64 public class NetworkActivity extends Activity {
65     public static final String WIFI = "Wi-Fi";
66     public static final String ANY = "Any";
67     private static final String URL =
68             "http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest";
69 
70     // Whether there is a Wi-Fi connection.
71     private static boolean wifiConnected = false;
72     // Whether there is a mobile connection.
73     private static boolean mobileConnected = false;
74     // Whether the display should be refreshed.
75     public static boolean refreshDisplay = true;
76 
77     // The user's current network preference setting.
78     public static String sPref = null;
79 
80     // The BroadcastReceiver that tracks network connectivity changes.
81     private NetworkReceiver receiver = new NetworkReceiver();
82 
83     @Override
onCreate(Bundle savedInstanceState)84     public void onCreate(Bundle savedInstanceState) {
85         super.onCreate(savedInstanceState);
86 
87         // Register BroadcastReceiver to track connection changes.
88         IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
89         receiver = new NetworkReceiver();
90         this.registerReceiver(receiver, filter);
91     }
92 
93     // Refreshes the display if the network connection and the
94     // pref settings allow it.
95     @Override
onStart()96     public void onStart() {
97         super.onStart();
98 
99         // Gets the user's network preference settings
100         SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
101 
102         // Retrieves a string value for the preferences. The second parameter
103         // is the default value to use if a preference value is not found.
104         sPref = sharedPrefs.getString("listPref", "Wi-Fi");
105 
106         updateConnectedFlags();
107 
108         // Only loads the page if refreshDisplay is true. Otherwise, keeps previous
109         // display. For example, if the user has set "Wi-Fi only" in prefs and the
110         // device loses its Wi-Fi connection midway through the user using the app,
111         // you don't want to refresh the display--this would force the display of
112         // an error page instead of stackoverflow.com content.
113         if (refreshDisplay) {
114             loadPage();
115         }
116     }
117 
118     @Override
onDestroy()119     public void onDestroy() {
120         super.onDestroy();
121         if (receiver != null) {
122             this.unregisterReceiver(receiver);
123         }
124     }
125 
126     // Checks the network connection and sets the wifiConnected and mobileConnected
127     // variables accordingly.
updateConnectedFlags()128     private void updateConnectedFlags() {
129         ConnectivityManager connMgr =
130                 (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
131 
132         NetworkInfo activeInfo = connMgr.getActiveNetworkInfo();
133         if (activeInfo != null && activeInfo.isConnected()) {
134             wifiConnected = activeInfo.getType() == ConnectivityManager.TYPE_WIFI;
135             mobileConnected = activeInfo.getType() == ConnectivityManager.TYPE_MOBILE;
136         } else {
137             wifiConnected = false;
138             mobileConnected = false;
139         }
140     }
141 
142     // Uses AsyncTask subclass to download the XML feed from stackoverflow.com.
143     // This avoids UI lock up. To prevent network operations from
144     // causing a delay that results in a poor user experience, always perform
145     // network operations on a separate thread from the UI.
loadPage()146     private void loadPage() {
147         if (((sPref.equals(ANY)) && (wifiConnected || mobileConnected))
148                 || ((sPref.equals(WIFI)) && (wifiConnected))) {
149             // AsyncTask subclass
150             new DownloadXmlTask().execute(URL);
151         } else {
152             showErrorPage();
153         }
154     }
155 
156     // Displays an error if the app is unable to load content.
showErrorPage()157     private void showErrorPage() {
158         setContentView(R.layout.main);
159 
160         // The specified network connection is not available. Displays error message.
161         WebView myWebView = findViewById(R.id.webview);
162         myWebView.loadData(getResources().getString(R.string.connection_error),
163                 "text/html", null);
164     }
165 
166     // Populates the activity's options menu.
167     @Override
onCreateOptionsMenu(Menu menu)168     public boolean onCreateOptionsMenu(Menu menu) {
169         MenuInflater inflater = getMenuInflater();
170         inflater.inflate(R.menu.mainmenu, menu);
171         return true;
172     }
173 
174     // Handles the user's menu selection.
175     @Override
onOptionsItemSelected(MenuItem item)176     public boolean onOptionsItemSelected(MenuItem item) {
177         switch (item.getItemId()) {
178         case R.id.settings:
179                 Intent settingsActivity = new Intent(getBaseContext(), SettingsActivity.class);
180                 startActivity(settingsActivity);
181                 return true;
182         case R.id.refresh:
183                 loadPage();
184                 return true;
185         default:
186                 return super.onOptionsItemSelected(item);
187         }
188     }
189 
190     // Implementation of AsyncTask used to download XML feed from stackoverflow.com.
191     private class DownloadXmlTask extends AsyncTask<String, Void, String> {
192 
193         @Override
doInBackground(String... urls)194         protected String doInBackground(String... urls) {
195             try {
196                 return loadXmlFromNetwork(urls[0]);
197             } catch (IOException e) {
198                 return getResources().getString(R.string.connection_error);
199             } catch (XmlPullParserException e) {
200                 return getResources().getString(R.string.xml_error);
201             }
202         }
203 
204         @Override
onPostExecute(String result)205         protected void onPostExecute(String result) {
206             setContentView(R.layout.main);
207             // Displays the HTML string in the UI via a WebView
208             WebView myWebView = findViewById(R.id.webview);
209             myWebView.loadData(result, "text/html", null);
210         }
211     }
212 
213     // Uploads XML from stackoverflow.com, parses it, and combines it with
214     // HTML markup. Returns HTML string.
loadXmlFromNetwork(String urlString)215     private String loadXmlFromNetwork(String urlString) throws XmlPullParserException, IOException {
216         InputStream stream = null;
217         StackOverflowXmlParser stackOverflowXmlParser = new StackOverflowXmlParser();
218         List<Entry> entries = null;
219         String title = null;
220         String url = null;
221         String summary = null;
222         Calendar rightNow = Calendar.getInstance();
223         DateFormat formatter = new SimpleDateFormat("MMM dd h:mmaa");
224 
225         // Checks whether the user set the preference to include summary text
226         SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
227         boolean pref = sharedPrefs.getBoolean("summaryPref", false);
228 
229         StringBuilder htmlString = new StringBuilder();
230         htmlString.append("<h3>" + getResources().getString(R.string.page_title) + "</h3>");
231         htmlString.append("<em>" + getResources().getString(R.string.updated) + " " +
232                 formatter.format(rightNow.getTime()) + "</em>");
233 
234         try {
235             stream = downloadUrl(urlString);
236             entries = stackOverflowXmlParser.parse(stream);
237         // Makes sure that the InputStream is closed after the app is
238         // finished using it.
239         } finally {
240             if (stream != null) {
241                 stream.close();
242             }
243         }
244 
245         // StackOverflowXmlParser returns a List (called "entries") of Entry objects.
246         // Each Entry object represents a single post in the XML feed.
247         // This section processes the entries list to combine each entry with HTML markup.
248         // Each entry is displayed in the UI as a link that optionally includes
249         // a text summary.
250         for (Entry entry : entries) {
251             htmlString.append("<p><a href='");
252             htmlString.append(entry.link);
253             htmlString.append("'>" + entry.title + "</a></p>");
254             // If the user set the preference to include summary text,
255             // adds it to the display.
256             if (pref) {
257                 htmlString.append(entry.summary);
258             }
259         }
260         return htmlString.toString();
261     }
262 
263     // Given a string representation of a URL, sets up a connection and gets
264     // an input stream.
downloadUrl(String urlString)265     private InputStream downloadUrl(String urlString) throws IOException {
266         URL url = new URL(urlString);
267         HttpURLConnection conn = (HttpURLConnection) url.openConnection();
268         conn.setReadTimeout(10000 /* milliseconds */);
269         conn.setConnectTimeout(15000 /* milliseconds */);
270         conn.setRequestMethod("GET");
271         conn.setDoInput(true);
272         // Starts the query
273         conn.connect();
274         InputStream stream = conn.getInputStream();
275         return stream;
276     }
277 
278     /**
279      *
280      * This BroadcastReceiver intercepts the android.net.ConnectivityManager.CONNECTIVITY_ACTION,
281      * which indicates a connection change. It checks whether the type is TYPE_WIFI.
282      * If it is, it checks whether Wi-Fi is connected and sets the wifiConnected flag in the
283      * main activity accordingly.
284      *
285      */
286     public class NetworkReceiver extends BroadcastReceiver {
287 
288         @Override
onReceive(Context context, Intent intent)289         public void onReceive(Context context, Intent intent) {
290             ConnectivityManager connMgr =
291                     (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
292             NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
293 
294             // Checks the user prefs and the network connection. Based on the result, decides
295             // whether
296             // to refresh the display or keep the current display.
297             // If the userpref is Wi-Fi only, checks to see if the device has a Wi-Fi connection.
298             if (WIFI.equals(sPref) && networkInfo != null
299                     && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
300                 // If device has its Wi-Fi connection, sets refreshDisplay
301                 // to true. This causes the display to be refreshed when the user
302                 // returns to the app.
303                 refreshDisplay = true;
304                 Toast.makeText(context, R.string.wifi_connected, Toast.LENGTH_SHORT).show();
305 
306                 // If the setting is ANY network and there is a network connection
307                 // (which by process of elimination would be mobile), sets refreshDisplay to true.
308             } else if (ANY.equals(sPref) && networkInfo != null) {
309                 refreshDisplay = true;
310 
311                 // Otherwise, the app can't download content--either because there is no network
312                 // connection (mobile or Wi-Fi), or because the pref setting is WIFI, and there
313                 // is no Wi-Fi connection.
314                 // Sets refreshDisplay to false.
315             } else {
316                 refreshDisplay = false;
317                 Toast.makeText(context, R.string.lost_connection, Toast.LENGTH_SHORT).show();
318             }
319         }
320     }
321 }
322