1 /*
2  * Copyright (C) 2016 Google Inc. All Rights Reserved.
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 package com.example.android.wearable.wear.wearhighbandwidthnetworking;
17 
18 import android.app.Activity;
19 import android.content.Context;
20 import android.content.Intent;
21 import android.net.ConnectivityManager;
22 import android.net.Network;
23 import android.net.NetworkCapabilities;
24 import android.net.NetworkRequest;
25 import android.os.Bundle;
26 import android.os.Handler;
27 import android.os.Message;
28 import android.util.Log;
29 import android.view.View;
30 import android.view.WindowManager;
31 import android.widget.ImageView;
32 import android.widget.TextView;
33 
34 import java.util.concurrent.TimeUnit;
35 
36 /**
37  * This sample demonstrates how to determine if a high-bandwidth network is available for use cases
38  * that require a minimum network bandwidth, such as streaming media or downloading large files.
39  * In addition, the sample demonstrates best practices for asking a user to add a new Wi-Fi network
40  * for high-bandwidth network operations, if currently available networks are inadequate.
41  */
42 public class MainActivity extends Activity  {
43     private static final String LOG_TAG = MainActivity.class.getSimpleName();
44 
45     // Intent action for sending the user directly to the add Wi-Fi network activity.
46     private static final String ACTION_ADD_NETWORK_SETTINGS =
47             "com.google.android.clockwork.settings.connectivity.wifi.ADD_NETWORK_SETTINGS";
48 
49     // Message to notify the network request timout handler that too much time has passed.
50     private static final int MESSAGE_CONNECTIVITY_TIMEOUT = 1;
51 
52     // How long the app should wait trying to connect to a sufficient high-bandwidth network before
53     // asking the user to add a new Wi-Fi network.
54     private static final long NETWORK_CONNECTIVITY_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
55 
56     // The minimum network bandwidth required by the app for high-bandwidth operations.
57     private static final int MIN_NETWORK_BANDWIDTH_KBPS = 10000;
58 
59     private ConnectivityManager mConnectivityManager;
60     private ConnectivityManager.NetworkCallback mNetworkCallback;
61 
62     // Handler for dealing with network connection timeouts.
63     private Handler mHandler;
64 
65     private ImageView mConnectivityIcon;
66     private TextView mConnectivityText;
67 
68     private View mButton;
69     private ImageView mButtonIcon;
70     private TextView mButtonText;
71     private TextView mInfoText;
72     private View mProgressBar;
73 
74     // Tags added to the button in the UI to detect what operation the user has requested.
75     // These are required since the app reuses the button for different states of the app/UI.
76     // See onButtonClick() for how these tags are used.
77     static final String TAG_REQUEST_NETWORK = "REQUEST_NETWORK";
78     static final String TAG_RELEASE_NETWORK = "RELEASE_NETWORK";
79     static final String TAG_ADD_WIFI = "ADD_WIFI";
80 
81     // These constants are used by setUiState() to determine what information to display in the UI,
82     // as this app reuses UI components for the various states of the app, which is dependent on
83     // the state of the network.
84     static final int UI_STATE_REQUEST_NETWORK = 1;
85     static final int UI_STATE_REQUESTING_NETWORK = 2;
86     static final int UI_STATE_NETWORK_CONNECTED = 3;
87     static final int UI_STATE_CONNECTION_TIMEOUT = 4;
88 
89     @Override
onCreate(Bundle savedInstanceState)90     protected void onCreate(Bundle savedInstanceState) {
91         super.onCreate(savedInstanceState);
92         setContentView(R.layout.activity_main);
93 
94         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
95 
96         mConnectivityIcon = (ImageView) findViewById(R.id.connectivity_icon);
97         mConnectivityText = (TextView) findViewById(R.id.connectivity_text);
98 
99         mProgressBar = findViewById(R.id.progress_bar);
100 
101         mButton = findViewById(R.id.button);
102         mButton.setTag(TAG_REQUEST_NETWORK);
103         mButtonIcon = (ImageView) findViewById(R.id.button_icon);
104         mButtonText = (TextView) findViewById(R.id.button_label);
105 
106         mInfoText = (TextView) findViewById(R.id.info_text);
107 
108         mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
109 
110         mHandler = new Handler() {
111             @Override
112             public void handleMessage(Message msg) {
113                 switch (msg.what) {
114                     case MESSAGE_CONNECTIVITY_TIMEOUT:
115                         Log.d(LOG_TAG, "Network connection timeout");
116                         setUiState(UI_STATE_CONNECTION_TIMEOUT);
117                         unregisterNetworkCallback();
118                         break;
119                 }
120             }
121         };
122     }
123 
124     @Override
onStop()125     public void onStop() {
126         releaseHighBandwidthNetwork();
127         super.onStop();
128     }
129 
130     @Override
onResume()131     public void onResume() {
132         super.onResume();
133 
134         if (isNetworkHighBandwidth()) {
135             setUiState(UI_STATE_NETWORK_CONNECTED);
136         } else {
137             setUiState(UI_STATE_REQUEST_NETWORK);
138         }
139     }
140 
unregisterNetworkCallback()141     private void unregisterNetworkCallback() {
142         if (mNetworkCallback != null) {
143             Log.d(LOG_TAG, "Unregistering network callback");
144             mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
145             mNetworkCallback = null;
146         }
147     }
148 
149     // Determine if there is a high-bandwidth network exists. Checks both the active
150     // and bound networks. Returns false if no network is available (low or high-bandwidth).
isNetworkHighBandwidth()151     private boolean isNetworkHighBandwidth() {
152         Network network = mConnectivityManager.getBoundNetworkForProcess();
153         network = network == null ? mConnectivityManager.getActiveNetwork() : network;
154         if (network == null) {
155             return false;
156         }
157 
158         // requires android.permission.ACCESS_NETWORK_STATE
159         int bandwidth = mConnectivityManager
160                 .getNetworkCapabilities(network).getLinkDownstreamBandwidthKbps();
161 
162         if (bandwidth >= MIN_NETWORK_BANDWIDTH_KBPS) {
163             return true;
164         }
165 
166         return false;
167     }
168 
requestHighBandwidthNetwork()169     private void requestHighBandwidthNetwork() {
170         // Before requesting a high-bandwidth network, ensure prior requests are invalidated.
171         unregisterNetworkCallback();
172 
173         Log.d(LOG_TAG, "Requesting high-bandwidth network");
174 
175         // Requesting an unmetered network may prevent you from connecting to the cellular
176         // network on the user's watch or phone; however, unless you explicitly ask for permission
177         // to a access the user's cellular network, you should request an unmetered network.
178         NetworkRequest request = new NetworkRequest.Builder()
179                 .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
180                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
181                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
182                 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
183                 .build();
184 
185         mNetworkCallback = new ConnectivityManager.NetworkCallback() {
186             @Override
187             public void onAvailable(final Network network) {
188                 mHandler.removeMessages(MESSAGE_CONNECTIVITY_TIMEOUT);
189 
190                 runOnUiThread(new Runnable() {
191                     @Override
192                     public void run() {
193                         // requires android.permission.INTERNET
194                         if (!mConnectivityManager.bindProcessToNetwork(network)) {
195                             Log.e(LOG_TAG, "ConnectivityManager.bindProcessToNetwork()"
196                                     + " requires android.permission.INTERNET");
197                             setUiState(UI_STATE_REQUEST_NETWORK);
198                         } else {
199                             Log.d(LOG_TAG, "Network available");
200                             setUiState(UI_STATE_NETWORK_CONNECTED);
201                         }
202                     }
203                 });
204             }
205 
206             @Override
207             public void onCapabilitiesChanged(Network network,
208                                               NetworkCapabilities networkCapabilities) {
209                 runOnUiThread(new Runnable() {
210                     @Override
211                     public void run() {
212                         Log.d(LOG_TAG, "Network capabilities changed");
213                     }
214                 });
215             }
216 
217             @Override
218             public void onLost(Network network) {
219                 Log.d(LOG_TAG, "Network lost");
220 
221                 runOnUiThread(new Runnable() {
222                     @Override
223                     public void run() {
224                         setUiState(UI_STATE_REQUEST_NETWORK);
225                     }
226                 });
227             }
228         };
229 
230         // requires android.permission.CHANGE_NETWORK_STATE
231         mConnectivityManager.requestNetwork(request, mNetworkCallback);
232 
233         mHandler.sendMessageDelayed(
234                 mHandler.obtainMessage(MESSAGE_CONNECTIVITY_TIMEOUT),
235                 NETWORK_CONNECTIVITY_TIMEOUT_MS);
236     }
237 
releaseHighBandwidthNetwork()238     private void releaseHighBandwidthNetwork() {
239         mConnectivityManager.bindProcessToNetwork(null);
240         unregisterNetworkCallback();
241     }
242 
addWifiNetwork()243     private void addWifiNetwork() {
244         // requires android.permission.CHANGE_WIFI_STATE
245         startActivity(new Intent(ACTION_ADD_NETWORK_SETTINGS));
246     }
247 
248     /**
249      * Click handler for the button in the UI. The view tag is used to determine the specific
250      * function of the button.
251      *
252      * @param view The view that was clicked
253      */
onButtonClick(View view)254     public void onButtonClick(View view) {
255         switch (view.getTag().toString()) {
256             case TAG_REQUEST_NETWORK:
257                 requestHighBandwidthNetwork();
258                 setUiState(UI_STATE_REQUESTING_NETWORK);
259                 break;
260 
261             case TAG_RELEASE_NETWORK:
262                 releaseHighBandwidthNetwork();
263                 setUiState(UI_STATE_REQUEST_NETWORK);
264                 break;
265 
266             case TAG_ADD_WIFI:
267                 addWifiNetwork();
268                 break;
269         }
270     }
271 
272     // Sets the text and icons the connectivity indicator, button, and info text in the app UI,
273     // which are all reused for the various states of the app and network connectivity. Also,
274     // will show/hide a progress bar, which is dependent on the state of the network connectivity
275     // request.
setUiState(int uiState)276     private void setUiState(int uiState) {
277         switch (uiState) {
278             case UI_STATE_REQUEST_NETWORK:
279                 if (isNetworkHighBandwidth()) {
280                     mConnectivityIcon.setImageResource(R.drawable.ic_cloud_happy);
281                     mConnectivityText.setText(R.string.network_fast);
282                 } else {
283                     mConnectivityIcon.setImageResource(R.drawable.ic_cloud_sad);
284                     mConnectivityText.setText(R.string.network_slow);
285                 }
286 
287                 mButton.setTag(TAG_REQUEST_NETWORK);
288                 mButtonIcon.setImageResource(R.drawable.ic_fast_network);
289                 mButtonText.setText(R.string.button_request_network);
290                 mInfoText.setText(R.string.info_request_network);
291 
292                 break;
293 
294             case UI_STATE_REQUESTING_NETWORK:
295                 mConnectivityIcon.setImageResource(R.drawable.ic_cloud_disconnected);
296                 mConnectivityText.setText(R.string.network_connecting);
297 
298                 mProgressBar.setVisibility(View.VISIBLE);
299                 mInfoText.setVisibility(View.GONE);
300                 mButton.setVisibility(View.GONE);
301 
302                 break;
303 
304             case UI_STATE_NETWORK_CONNECTED:
305                 if (isNetworkHighBandwidth()) {
306                     mConnectivityIcon.setImageResource(R.drawable.ic_cloud_happy);
307                     mConnectivityText.setText(R.string.network_fast);
308                 } else {
309                     mConnectivityIcon.setImageResource(R.drawable.ic_cloud_sad);
310                     mConnectivityText.setText(R.string.network_slow);
311                 }
312 
313                 mProgressBar.setVisibility(View.GONE);
314                 mInfoText.setVisibility(View.VISIBLE);
315                 mButton.setVisibility(View.VISIBLE);
316 
317                 mButton.setTag(TAG_RELEASE_NETWORK);
318                 mButtonIcon.setImageResource(R.drawable.ic_no_network);
319                 mButtonText.setText(R.string.button_release_network);
320                 mInfoText.setText(R.string.info_release_network);
321 
322                 break;
323 
324             case UI_STATE_CONNECTION_TIMEOUT:
325                 mConnectivityIcon.setImageResource(R.drawable.ic_cloud_disconnected);
326                 mConnectivityText.setText(R.string.network_disconnected);
327 
328                 mProgressBar.setVisibility(View.GONE);
329                 mInfoText.setVisibility(View.VISIBLE);
330                 mButton.setVisibility(View.VISIBLE);
331 
332                 mButton.setTag(TAG_ADD_WIFI);
333                 mButtonIcon.setImageResource(R.drawable.ic_wifi_network);
334                 mButtonText.setText(R.string.button_add_wifi);
335                 mInfoText.setText(R.string.info_add_wifi);
336 
337                 break;
338         }
339     }
340 }