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
15package com.example.android.networkusage;
16
17import android.app.Activity;
18import android.content.BroadcastReceiver;
19import android.content.Context;
20import android.content.Intent;
21import android.content.IntentFilter;
22import android.content.SharedPreferences;
23import android.net.ConnectivityManager;
24import android.net.NetworkInfo;
25import android.os.AsyncTask;
26import android.os.Bundle;
27import android.preference.PreferenceManager;
28import android.view.Menu;
29import android.view.MenuInflater;
30import android.view.MenuItem;
31import android.webkit.WebView;
32import android.widget.Toast;
33
34import com.example.android.networkusage.R;
35import com.example.android.networkusage.StackOverflowXmlParser.Entry;
36
37import org.xmlpull.v1.XmlPullParserException;
38
39import java.io.IOException;
40import java.io.InputStream;
41import java.net.HttpURLConnection;
42import java.net.URL;
43import java.text.DateFormat;
44import java.text.SimpleDateFormat;
45import java.util.Calendar;
46import 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 */
64public 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
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
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
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.
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.
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.
157    private void showErrorPage() {
158        setContentView(R.layout.main);
159
160        // The specified network connection is not available. Displays error message.
161        WebView myWebView = (WebView) 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
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
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
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
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 = (WebView) 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.
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.
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
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