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