1 /* 2 * Copyright (C) 2014 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.android.tv.settings.connectivity; 18 19 import android.app.Activity; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.res.Resources; 23 import android.net.IpConfiguration; 24 import android.net.IpConfiguration.IpAssignment; 25 import android.net.IpConfiguration.ProxySettings; 26 import android.net.wifi.WifiConfiguration; 27 import android.net.wifi.ScanResult; 28 import android.net.wifi.WifiInfo; 29 import android.net.wifi.WifiManager; 30 import android.os.Bundle; 31 import android.os.Handler; 32 import android.os.SystemClock; 33 import android.text.TextUtils; 34 import android.util.Pair; 35 36 import com.android.tv.settings.dialog.SettingsLayoutActivity; 37 import com.android.tv.settings.dialog.Layout; 38 import com.android.tv.settings.dialog.Layout.Header; 39 import com.android.tv.settings.dialog.Layout.Action; 40 import com.android.tv.settings.dialog.Layout.Status; 41 import com.android.tv.settings.dialog.Layout.Static; 42 import com.android.tv.settings.dialog.Layout.StringGetter; 43 import com.android.tv.settings.dialog.Layout.LayoutGetter; 44 import com.android.tv.settings.R; 45 46 import java.util.ArrayList; 47 import java.util.Collections; 48 import java.util.HashMap; 49 import java.util.List; 50 51 /** 52 * Activity to manage network settings. 53 */ 54 public class NetworkActivity extends SettingsLayoutActivity implements 55 ConnectivityListener.Listener, ConnectivityListener.WifiNetworkListener { 56 57 private static final int REQUEST_CODE_ADVANCED_OPTIONS = 1; 58 private static final int WIFI_SCAN_INTERVAL_CAP_MILLIS = 10 * 1000; 59 private static final int WIFI_UI_REFRESH_INTERVAL_CAP_MILLIS = 15 * 1000; 60 61 private ConnectivityListener mConnectivityListener; 62 private Resources mRes; 63 private Handler mHandler = new Handler(); 64 private final Runnable mRefreshWifiAccessPoints = new Runnable() { 65 @Override 66 public void run() { 67 mConnectivityListener.scanWifiAccessPoints(NetworkActivity.this); 68 mHandler.removeCallbacks(mRefreshWifiAccessPoints); 69 mHandler.postDelayed(mRefreshWifiAccessPoints, WIFI_SCAN_INTERVAL_CAP_MILLIS); 70 } 71 }; 72 73 @Override 74 protected void onCreate(Bundle savedInstanceState) { 75 mRes = getResources(); 76 mConnectivityListener = new ConnectivityListener(this, this); 77 // The ConectivityListenter must be started before calling "super.OnCreate(.)" to ensure 78 // that connectivity status is available before the layout is constructed. 79 mConnectivityListener.start(); 80 super.onCreate(savedInstanceState); 81 } 82 83 @Override 84 public void onResume() { 85 mConnectivityListener.start(); 86 mHandler.removeCallbacks(mRefreshWifiAccessPoints); 87 mHandler.post(mRefreshWifiAccessPoints); 88 onConnectivityChange(null); 89 90 // TODO(lanechr): It's an anti-pattern that we have to notify Layout here; see b/18889239. 91 mWifiAdvancedLayout.refreshView(); 92 93 super.onResume(); 94 } 95 96 @Override 97 protected void onPause() { 98 mHandler.removeCallbacks(mRefreshWifiAccessPoints); 99 mConnectivityListener.stop(); 100 super.onPause(); 101 } 102 103 @Override 104 protected void onDestroy(){ 105 mConnectivityListener = null; 106 super.onDestroy(); 107 } 108 109 // ConnectivityListener.Listener overrides. 110 @Override 111 public void onConnectivityChange(Intent intent) { 112 mEthernetConnectedDescription.refreshView(); 113 mWifiConnectedDescription.refreshView(); 114 onWifiListChanged(); 115 } 116 117 @Override 118 public void onWifiListChanged() { 119 mWifiShortListLayout.onWifiListChanged(); 120 mWifiAllListLayout.onWifiListChanged(); 121 } 122 123 StringGetter mEthernetConnectedDescription = new StringGetter() { 124 private boolean lastIsEthernetConnected; 125 @Override 126 public String get() { 127 lastIsEthernetConnected = 128 mConnectivityListener.getConnectivityStatus().isEthernetConnected(); 129 int resId = lastIsEthernetConnected ? R.string.connected : R.string.not_connected; 130 return mRes.getString(resId); 131 } 132 @Override 133 public void refreshView() { 134 if (mConnectivityListener.getConnectivityStatus().isEthernetConnected() != 135 lastIsEthernetConnected) { 136 super.refreshView(); 137 } 138 } 139 }; 140 141 StringGetter mWifiConnectedDescription = new StringGetter() { 142 private boolean lastIsWifiConnected; 143 @Override 144 public String get() { 145 lastIsWifiConnected = mConnectivityListener.getConnectivityStatus().isWifiConnected(); 146 int resId = lastIsWifiConnected ? R.string.connected : R.string.not_connected; 147 return mRes.getString(resId); 148 } 149 @Override 150 public void refreshView() { 151 if (mConnectivityListener.getConnectivityStatus().isWifiConnected() != 152 lastIsWifiConnected) { 153 super.refreshView(); 154 } 155 } 156 }; 157 158 StringGetter mEthernetIPAddress = new StringGetter() { 159 public String get() { 160 ConnectivityListener.ConnectivityStatus status = 161 mConnectivityListener.getConnectivityStatus(); 162 if (status.isEthernetConnected()) { 163 return mConnectivityListener.getEthernetIpAddress(); 164 } else { 165 return ""; 166 } 167 } 168 }; 169 170 StringGetter mEthernetMacAddress = new StringGetter() { 171 public String get() { 172 return mConnectivityListener.getEthernetMacAddress(); 173 } 174 }; 175 176 LayoutGetter mEthernetAdvancedLayout = new LayoutGetter() { 177 public Layout get() { 178 Layout layout = new Layout(); 179 // Do not check Ethernet's availability here 180 // because it might not be active due to the invalid configuration. 181 IpConfiguration ipConfiguration = mConnectivityListener.getIpConfiguration(); 182 if (ipConfiguration != null) { 183 int proxySettingsResourceId = 184 (ipConfiguration.getProxySettings() == ProxySettings.STATIC) ? 185 R.string.wifi_action_proxy_manual : 186 R.string.wifi_action_proxy_none; 187 int ipSettingsResourceId = 188 (ipConfiguration.getIpAssignment() == IpAssignment.STATIC) ? 189 R.string.wifi_action_static : 190 R.string.wifi_action_dhcp; 191 layout 192 .add(new Action.Builder(mRes, ACTION_ETHERNET_PROXY_SETTINGS) 193 .title(R.string.title_wifi_proxy_settings) 194 .description(proxySettingsResourceId).build()) 195 .add(new Action.Builder(mRes, ACTION_ETHERNET_IP_SETTINGS) 196 .title(R.string.title_wifi_ip_settings) 197 .description(ipSettingsResourceId).build()); 198 } else { 199 layout 200 .add(new Status.Builder(mRes) 201 .title(R.string.title_internet_connection) 202 .description(R.string.not_connected).build()); 203 } 204 return layout; 205 } 206 }; 207 208 LayoutGetter mEthernetLayout = new LayoutGetter() { 209 public Layout get() { 210 boolean ethernetConnected = 211 mConnectivityListener.getConnectivityStatus().isEthernetConnected(); 212 if (ethernetConnected) { 213 return new Layout() 214 .add(new Status.Builder(mRes).title(R.string.title_internet_connection) 215 .description(R.string.connected).build()) 216 .add(new Status.Builder(mRes) 217 .title(R.string.title_ip_address) 218 .description(mEthernetIPAddress) 219 .build()) 220 .add(new Status.Builder(mRes) 221 .title(R.string.title_mac_address) 222 .description(mEthernetMacAddress) 223 .build()) 224 .add(new Header.Builder(mRes) 225 .title(R.string.wifi_action_advanced_options_title).build() 226 .add(mEthernetAdvancedLayout) 227 ); 228 229 } else { 230 return new Layout() 231 .add(new Status.Builder(mRes) 232 .title(R.string.title_internet_connection) 233 .description(R.string.not_connected) 234 .build()) 235 .add(new Header.Builder(mRes) 236 .title(R.string.wifi_action_advanced_options_title).build() 237 .add(mEthernetAdvancedLayout) 238 ); 239 } 240 } 241 }; 242 243 private static final int NUMBER_SIGNAL_LEVELS = 4; 244 private static final int ACTION_WIFI_FORGET_NETWORK = 1; 245 private static final int ACTION_WIFI_PROXY_SETTINGS = 4; 246 private static final int ACTION_WIFI_IP_SETTINGS = 5; 247 private static final int ACTION_ETHERNET_PROXY_SETTINGS = 6; 248 private static final int ACTION_ETHERNET_IP_SETTINGS = 7; 249 250 private final Context mContext = this; 251 252 private String getSignalStrength() { 253 String[] signalLevels = mRes.getStringArray(R.array.wifi_signal_strength); 254 int strength = mConnectivityListener.getWifiSignalStrength(signalLevels.length); 255 return signalLevels[strength]; 256 } 257 258 LayoutGetter mWifiInfoLayout = new LayoutGetter() { 259 public Layout get() { 260 Layout layout = new Layout(); 261 ConnectivityListener.ConnectivityStatus status = 262 mConnectivityListener.getConnectivityStatus(); 263 boolean isConnected = status.isWifiConnected(); 264 if (isConnected) { 265 layout 266 .add(new Status.Builder(mRes) 267 .title(R.string.title_internet_connection) 268 .description(R.string.connected).build()) 269 .add(new Status.Builder(mRes) 270 .title(R.string.title_ip_address) 271 .description(mConnectivityListener.getWifiIpAddress()).build()) 272 .add(new Status.Builder(mRes) 273 .title(R.string.title_mac_address) 274 .description(mConnectivityListener.getWifiMacAddress()).build()) 275 .add(new Status.Builder(mRes) 276 .title(R.string.title_signal_strength) 277 .description(getSignalStrength()).build()); 278 } else { 279 layout 280 .add(new Status.Builder(mRes) 281 .title(R.string.title_internet_connection) 282 .description(R.string.not_connected).build()); 283 } 284 return layout; 285 } 286 }; 287 288 LayoutGetter mWifiAdvancedLayout = new LayoutGetter() { 289 public Layout get() { 290 Layout layout = new Layout(); 291 WifiConfiguration wifiConfiguration = mConnectivityListener.getWifiConfiguration(); 292 if (wifiConfiguration != null) { 293 int proxySettingsResourceId = 294 (wifiConfiguration.getProxySettings() == ProxySettings.NONE) ? 295 R.string.wifi_action_proxy_none : 296 R.string.wifi_action_proxy_manual; 297 int ipSettingsResourceId = 298 (wifiConfiguration.getIpAssignment() == IpAssignment.STATIC) ? 299 R.string.wifi_action_static : 300 R.string.wifi_action_dhcp; 301 layout 302 .add(new Action.Builder(mRes, ACTION_WIFI_PROXY_SETTINGS) 303 .title(R.string.title_wifi_proxy_settings) 304 .description(proxySettingsResourceId).build()) 305 .add(new Action.Builder(mRes, ACTION_WIFI_IP_SETTINGS) 306 .title(R.string.title_wifi_ip_settings) 307 .description(ipSettingsResourceId).build()); 308 } else { 309 layout 310 .add(new Status.Builder(mRes) 311 .title(R.string.title_internet_connection) 312 .description(R.string.not_connected).build()); 313 } 314 return layout; 315 } 316 }; 317 318 LayoutGetter mWifiLayout = new LayoutGetter() { 319 public Layout get() { 320 return new Layout() 321 .add(new Static.Builder(mRes) 322 .title(R.string.wifi_setting_available_networks) 323 .build()) 324 .add(mWifiShortListLayout) 325 .add(new Header.Builder(mRes) 326 .title(R.string.wifi_setting_see_all) 327 .build() 328 .add(mWifiAllListLayout) 329 ) 330 .add(new Static.Builder(mRes) 331 .title(R.string.wifi_setting_header_other_options) 332 .build()) 333 .add(new Action.Builder(mRes, 334 new Intent(NetworkActivity.this, WpsConnectionActivity.class)) 335 .title(R.string.wifi_setting_other_options_wps) 336 .build()) 337 .add(new Action.Builder(mRes, 338 new Intent(NetworkActivity.this, AddWifiNetworkActivity.class)) 339 .title(R.string.wifi_setting_other_options_add_network) 340 .build()); 341 } 342 }; 343 344 private void addWifiConnectedHeader(Layout layout, String SSID, int iconResId) { 345 layout 346 .add(new Header.Builder(mRes) 347 .title(SSID) 348 .icon(iconResId) 349 .description(R.string.connected).build() 350 .add(new Header.Builder(mRes) 351 .title(R.string.wifi_action_status_info).build() 352 .add(mWifiInfoLayout) 353 ) 354 .add(new Header.Builder(mRes) 355 .title(R.string.wifi_action_advanced_options_title).build() 356 .add(mWifiAdvancedLayout) 357 ) 358 .add(new Header.Builder(mRes) 359 .title(R.string.wifi_forget_network).build() 360 .add(new Action.Builder(mRes, ACTION_WIFI_FORGET_NETWORK) 361 .title(R.string.title_ok).build()) 362 .add(new Action.Builder(mRes, Action.ACTION_BACK) 363 .title(R.string.title_cancel).build()) 364 ) 365 ); 366 } 367 368 private class WifiListLayout extends LayoutGetter { 369 private final boolean mTop3EntriesOnly; 370 private String mSelectedTitle; 371 private long mLastWifiRefresh = 0; 372 373 private final Runnable mRefreshViewRunnable = new Runnable() { 374 @Override 375 public void run() { 376 Layout.Node selected = getSelectedNode(); 377 if (selected != null) { 378 mSelectedTitle = selected.getTitle(); 379 } 380 refreshView(); 381 } 382 }; 383 384 WifiListLayout(boolean top3EntriesOnly) { 385 mTop3EntriesOnly = top3EntriesOnly; 386 } 387 388 @Override 389 public Layout get() { 390 mLastWifiRefresh = SystemClock.elapsedRealtime(); 391 mHandler.removeCallbacks(mRefreshViewRunnable); 392 return initAvailableWifiNetworks(mTop3EntriesOnly, mSelectedTitle). 393 setSelectedByTitle(mSelectedTitle); 394 } 395 396 /** 397 * Wifi network list has changed and an eventual refresh of the UI is required. 398 * Rate limit the UI refresh to once per WIFI_UI_REFRESH_INTERVAL_CAP_MILLIS. 399 */ 400 public void onWifiListChanged() { 401 long now = SystemClock.elapsedRealtime(); 402 long millisToNextRefreshView = 403 WIFI_UI_REFRESH_INTERVAL_CAP_MILLIS - now + mLastWifiRefresh; 404 mHandler.removeCallbacks(mRefreshViewRunnable); 405 mHandler.postDelayed(mRefreshViewRunnable, millisToNextRefreshView); 406 } 407 408 /** 409 * Wifi network configuration has changed and an immediate refresh of the list of Wifi 410 * networks is required. 411 */ 412 public void onWifiListInvalidated() { 413 mHandler.removeCallbacks(mRefreshViewRunnable); 414 mHandler.post(mRefreshViewRunnable); 415 } 416 417 /** 418 * Create a list of available Wifi networks sorted by connection status (a connected Wifi 419 * network is shown at the first position on the list) and signal strength, with the 420 * provisio that the wireless network with SSID "mustHave" should be included in the list 421 * even if it would be otherwise excluded. 422 * 423 * @param top3EntriesOnly Show only 3 entries in the list. 424 * @param mustHave Include this wifi network in the list even if it would otherwise 425 * be excluded by virtue of inadequate signal strength. 426 */ 427 private Layout initAvailableWifiNetworks(boolean top3EntriesOnly, String mustHave) { 428 List<ScanResult> networks = mConnectivityListener.getAvailableNetworks(); 429 Layout layout = new Layout(); 430 if (networks.size() > 0) { 431 int maxItems = top3EntriesOnly ? 3 : Integer.MAX_VALUE; 432 // "networks" is already sorted by the signal strength and connection status. 433 // Generate a new list with size less than "maxItems" that ensures "mustHave" is 434 // included. 435 boolean haveMustHave = false; 436 List<ScanResult> displayList = new ArrayList<ScanResult>(); 437 for (ScanResult scanResult : networks) { 438 if (!haveMustHave && TextUtils.equals(scanResult.SSID, mustHave)) { 439 haveMustHave = true; 440 if (displayList.size() == maxItems) { 441 displayList.remove(maxItems-1); 442 } 443 displayList.add(scanResult); 444 } else if (displayList.size() < maxItems) { 445 displayList.add(scanResult); 446 } 447 if (haveMustHave && displayList.size() == maxItems) { 448 break; 449 } 450 } 451 452 // If a network is connected, it will be the first on the list. 453 boolean isConnected = 454 mConnectivityListener.getConnectivityStatus().isWifiConnected(); 455 for (ScanResult network : displayList) { 456 if (network != null) { 457 WifiSecurity security = WifiSecurity.getSecurity(network); 458 int signalLevel = WifiManager.calculateSignalLevel( 459 network.level, NUMBER_SIGNAL_LEVELS); 460 int imageResourceId = getNetworkIconRes(security.isOpen(), signalLevel); 461 462 if (isConnected) { 463 addWifiConnectedHeader(layout, network.SSID, imageResourceId); 464 } else { 465 Intent intent = 466 WifiConnectionActivity.createIntent(mContext, network, security); 467 String networkDescription = 468 security.isOpen() ? "" : security.getName(mContext); 469 layout.add(new Action.Builder(mRes, intent) 470 .title(network.SSID) 471 .icon(imageResourceId) 472 .description(networkDescription).build()); 473 } 474 } 475 isConnected = false; 476 } 477 } else { 478 layout.add(new Action.Builder(mRes, 0) 479 .title(R.string.title_wifi_no_networks_available).build()); 480 } 481 return layout; 482 } 483 }; 484 485 private final WifiListLayout mWifiShortListLayout = new WifiListLayout(true); 486 487 private final WifiListLayout mWifiAllListLayout = new WifiListLayout(false); 488 489 @Override 490 public Layout createLayout() { 491 // Note: This only updates the layout the activity is loaded, 492 // not if the user plugs/unplugs in an adapter. 493 if (mConnectivityListener.isEthernetAvailable()) { 494 return new Layout() 495 .breadcrumb(getString(R.string.header_category_device)) 496 .add(new Header.Builder(mRes) 497 .icon(R.drawable.ic_settings_wifi_4) 498 .title(R.string.connectivity_network) 499 .description(mWifiConnectedDescription) 500 .build() 501 .add(new Header.Builder(mRes) 502 .title(R.string.connectivity_wifi) 503 .contentIconRes(R.drawable.ic_settings_wifi_4) 504 .description(mWifiConnectedDescription) 505 .build() 506 .add(mWifiLayout)) 507 .add(new Header.Builder(mRes) 508 .title(R.string.connectivity_ethernet) 509 .contentIconRes(R.drawable.ic_settings_ethernet) 510 .description(mEthernetConnectedDescription) 511 .build() 512 .add(mEthernetLayout))); 513 } else { 514 // Only Wifi is available. 515 return new Layout() 516 .breadcrumb(getString(R.string.header_category_device)) 517 .add(new Header.Builder(mRes) 518 .icon(R.drawable.ic_settings_wifi_4) 519 .title(R.string.connectivity_wifi) 520 .description(mWifiConnectedDescription) 521 .build() 522 .add(mWifiLayout)); 523 } 524 } 525 526 @Override 527 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 528 if (requestCode == REQUEST_CODE_ADVANCED_OPTIONS && resultCode == RESULT_OK) { 529 //TODO make sure view reflects model deltas 530 } else { 531 super.onActivityResult(requestCode, resultCode, data); 532 } 533 } 534 535 @Override 536 public void onActionFocused(Layout.LayoutRow item) { 537 int resId = item.getContentIconRes(); 538 if (resId != 0) { 539 setIcon(resId); 540 } 541 } 542 543 @Override 544 public void onActionClicked(Action action) { 545 switch (action.getId()) { 546 case Action.ACTION_INTENT: 547 startActivityForResult(action.getIntent(), REQUEST_CODE_ADVANCED_OPTIONS); 548 break; 549 case ACTION_WIFI_FORGET_NETWORK: 550 mConnectivityListener.forgetWifiNetwork(); 551 goBackToTitle(mRes.getString(R.string.connectivity_wifi)); 552 mWifiShortListLayout.onWifiListInvalidated(); 553 mWifiAllListLayout.onWifiListInvalidated(); 554 break; 555 case ACTION_WIFI_PROXY_SETTINGS: { 556 int networkId = mConnectivityListener.getWifiNetworkId(); 557 if (networkId != -1) { 558 startActivityForResult(EditProxySettingsActivity.createIntent(this, networkId), 559 REQUEST_CODE_ADVANCED_OPTIONS); 560 } 561 break; 562 } 563 case ACTION_WIFI_IP_SETTINGS: { 564 int networkId = mConnectivityListener.getWifiNetworkId(); 565 if (networkId != -1) { 566 startActivityForResult(EditIpSettingsActivity.createIntent(this, networkId), 567 REQUEST_CODE_ADVANCED_OPTIONS); 568 } 569 break; 570 } 571 case ACTION_ETHERNET_PROXY_SETTINGS: { 572 int networkId = WifiConfiguration.INVALID_NETWORK_ID; 573 startActivityForResult(EditProxySettingsActivity.createIntent(this, networkId), 574 REQUEST_CODE_ADVANCED_OPTIONS); 575 break; 576 } 577 case ACTION_ETHERNET_IP_SETTINGS: { 578 int networkId = WifiConfiguration.INVALID_NETWORK_ID; 579 startActivityForResult(EditIpSettingsActivity.createIntent(this, networkId), 580 REQUEST_CODE_ADVANCED_OPTIONS); 581 break; 582 } 583 } 584 } 585 586 private int getNetworkIconRes(boolean isOpen, int signalLevel) { 587 int resourceId = R.drawable.ic_settings_wifi_not_connected; 588 589 if (isOpen) { 590 switch (signalLevel) { 591 case 0: 592 resourceId = R.drawable.ic_settings_wifi_1; 593 break; 594 case 1: 595 resourceId = R.drawable.ic_settings_wifi_2; 596 break; 597 case 2: 598 resourceId = R.drawable.ic_settings_wifi_3; 599 break; 600 case 3: 601 resourceId = R.drawable.ic_settings_wifi_4; 602 break; 603 } 604 } else { 605 switch (signalLevel) { 606 case 0: 607 resourceId = R.drawable.ic_settings_wifi_secure_1; 608 break; 609 case 1: 610 resourceId = R.drawable.ic_settings_wifi_secure_2; 611 break; 612 case 2: 613 resourceId = R.drawable.ic_settings_wifi_secure_3; 614 break; 615 case 3: 616 resourceId = R.drawable.ic_settings_wifi_secure_4; 617 break; 618 } 619 } 620 621 return resourceId; 622 } 623 } 624