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