1/*
2 * Copyright (C) 2015 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.about;
18
19import android.bluetooth.BluetoothAdapter;
20import android.content.BroadcastReceiver;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.net.ConnectivityManager;
25import android.net.LinkProperties;
26import android.net.wifi.WifiInfo;
27import android.net.wifi.WifiManager;
28import android.os.Build;
29import android.os.Bundle;
30import android.os.Handler;
31import android.os.Message;
32import android.os.SystemClock;
33import android.os.SystemProperties;
34import android.os.UserManager;
35import android.support.annotation.Nullable;
36import android.support.v17.preference.LeanbackPreferenceFragment;
37import android.support.v7.preference.Preference;
38import android.text.TextUtils;
39import android.text.format.DateUtils;
40
41import com.android.internal.util.ArrayUtils;
42import com.android.tv.settings.R;
43
44import java.lang.ref.WeakReference;
45import java.net.InetAddress;
46import java.util.Iterator;
47
48public class StatusFragment extends LeanbackPreferenceFragment {
49
50    private static final String KEY_BATTERY_STATUS = "battery_status";
51    private static final String KEY_BATTERY_LEVEL = "battery_level";
52    private static final String KEY_IP_ADDRESS = "wifi_ip_address";
53    private static final String KEY_WIFI_MAC_ADDRESS = "wifi_mac_address";
54    private static final String KEY_BT_ADDRESS = "bt_address";
55    private static final String KEY_SERIAL_NUMBER = "serial_number";
56    private static final String KEY_WIMAX_MAC_ADDRESS = "wimax_mac_address";
57    private static final String KEY_SIM_STATUS = "sim_status";
58    private static final String KEY_IMEI_INFO = "imei_info";
59
60    // Broadcasts to listen to for connectivity changes.
61    private static final String[] CONNECTIVITY_INTENTS = {
62            BluetoothAdapter.ACTION_STATE_CHANGED,
63            ConnectivityManager.CONNECTIVITY_ACTION,
64            WifiManager.LINK_CONFIGURATION_CHANGED_ACTION,
65            WifiManager.NETWORK_STATE_CHANGED_ACTION,
66    };
67
68    private static final int EVENT_UPDATE_STATS = 500;
69
70    private static final int EVENT_UPDATE_CONNECTIVITY = 600;
71
72    private ConnectivityManager mCM;
73    private WifiManager mWifiManager;
74
75    private Preference mUptime;
76    private Preference mBtAddress;
77    private Preference mIpAddress;
78    private Preference mWifiMacAddress;
79    private Preference mWimaxMacAddress;
80
81    private IntentFilter mConnectivityIntentFilter;
82    private final BroadcastReceiver mConnectivityReceiver = new BroadcastReceiver() {
83        @Override
84        public void onReceive(Context context, Intent intent) {
85            String action = intent.getAction();
86            if (ArrayUtils.contains(CONNECTIVITY_INTENTS, action)) {
87                mHandler.sendEmptyMessage(EVENT_UPDATE_CONNECTIVITY);
88            }
89        }
90    };
91
92    private Handler mHandler;
93
94    private static class MyHandler extends Handler {
95        private WeakReference<StatusFragment> mStatus;
96
97        public MyHandler(StatusFragment activity) {
98            mStatus = new WeakReference<>(activity);
99        }
100
101        @Override
102        public void handleMessage(Message msg) {
103            StatusFragment status = mStatus.get();
104            if (status == null) {
105                return;
106            }
107
108            switch (msg.what) {
109                case EVENT_UPDATE_STATS:
110                    status.updateTimes();
111                    sendEmptyMessageDelayed(EVENT_UPDATE_STATS, 1000);
112                    break;
113
114                case EVENT_UPDATE_CONNECTIVITY:
115                    status.updateConnectivity();
116                    break;
117            }
118        }
119    }
120
121    public static StatusFragment newInstance() {
122        return new StatusFragment();
123    }
124
125    @Override
126    public void onCreate(Bundle savedInstanceState) {
127        mHandler = new MyHandler(this);
128
129        mCM = (ConnectivityManager) getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
130        mWifiManager = (WifiManager) getActivity().getSystemService(Context.WIFI_SERVICE);
131
132        super.onCreate(savedInstanceState);
133    }
134
135    @Override
136    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
137        setPreferencesFromResource(R.xml.device_info_status, null);
138
139        // TODO: detect if we have a battery or not
140        removePreference(findPreference(KEY_BATTERY_LEVEL));
141        removePreference(findPreference(KEY_BATTERY_STATUS));
142        mBtAddress = findPreference(KEY_BT_ADDRESS);
143        mWifiMacAddress = findPreference(KEY_WIFI_MAC_ADDRESS);
144        mWimaxMacAddress = findPreference(KEY_WIMAX_MAC_ADDRESS);
145        mIpAddress = findPreference(KEY_IP_ADDRESS);
146        mUptime = findPreference("up_time");
147
148        if (!hasBluetooth()) {
149            getPreferenceScreen().removePreference(mBtAddress);
150            mBtAddress = null;
151        }
152
153        if (!hasWimax()) {
154            getPreferenceScreen().removePreference(mWimaxMacAddress);
155            mWimaxMacAddress = null;
156        }
157
158        mConnectivityIntentFilter = new IntentFilter();
159        for (String intent: CONNECTIVITY_INTENTS) {
160            mConnectivityIntentFilter.addAction(intent);
161        }
162
163        updateConnectivity();
164
165        final Preference serialPref = findPreference(KEY_SERIAL_NUMBER);
166        String serial = Build.SERIAL;
167        if (!TextUtils.isEmpty(serial)) {
168            serialPref.setSummary(serial);
169        } else {
170            removePreference(serialPref);
171        }
172
173        // Remove SimStatus and Imei for Secondary user as it access Phone b/19165700
174        // Also remove on Wi-Fi only devices.
175        //TODO: the bug above will surface in split system user mode.
176        if (!UserManager.get(getActivity()).isAdminUser()
177                || AboutFragment.isWifiOnly(getActivity())) {
178            removePreference(findPreference(KEY_SIM_STATUS));
179            removePreference(findPreference(KEY_IMEI_INFO));
180        }
181    }
182
183    private void removePreference(@Nullable Preference preference) {
184        if (preference != null) {
185            getPreferenceScreen().removePreference(preference);
186        }
187    }
188
189    private boolean hasBluetooth() {
190        return BluetoothAdapter.getDefaultAdapter() != null;
191    }
192
193    private boolean hasWimax() {
194        return  mCM.getNetworkInfo(ConnectivityManager.TYPE_WIMAX) != null;
195    }
196
197    @Override
198    public void onStart() {
199        super.onStart();
200        getActivity().registerReceiver(mConnectivityReceiver, mConnectivityIntentFilter,
201                android.Manifest.permission.CHANGE_NETWORK_STATE, null);
202        mHandler.sendEmptyMessage(EVENT_UPDATE_STATS);
203    }
204
205    @Override
206    public void onStop() {
207        super.onStop();
208        getActivity().unregisterReceiver(mConnectivityReceiver);
209        mHandler.removeMessages(EVENT_UPDATE_STATS);
210    }
211
212    private void setWimaxStatus() {
213        if (mWimaxMacAddress != null) {
214            String macAddress = SystemProperties.get("net.wimax.mac.address",
215                    getString(R.string.status_unavailable));
216            mWimaxMacAddress.setSummary(macAddress);
217        }
218    }
219
220    private void setWifiStatus() {
221        WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
222        String macAddress = wifiInfo == null ? null : wifiInfo.getMacAddress();
223        mWifiMacAddress.setSummary(!TextUtils.isEmpty(macAddress) ?
224                macAddress : getString(R.string.status_unavailable));
225    }
226
227    private void setIpAddressStatus() {
228        String ipAddress = getDefaultIpAddresses(mCM);
229        if (ipAddress != null) {
230            mIpAddress.setSummary(ipAddress);
231        } else {
232            mIpAddress.setSummary(R.string.status_unavailable);
233        }
234    }
235
236    private void setBtStatus() {
237        BluetoothAdapter bluetooth = BluetoothAdapter.getDefaultAdapter();
238        if (bluetooth != null && mBtAddress != null) {
239            String address = bluetooth.isEnabled() ? bluetooth.getAddress() : null;
240            if (!TextUtils.isEmpty(address)) {
241                // Convert the address to lowercase for consistency with the wifi MAC address.
242                mBtAddress.setSummary(address.toLowerCase());
243            } else {
244                mBtAddress.setSummary(R.string.status_unavailable);
245            }
246        }
247    }
248
249    void updateConnectivity() {
250        setWimaxStatus();
251        setWifiStatus();
252        setBtStatus();
253        setIpAddressStatus();
254    }
255
256    /**
257     * Returns the default link's IP addresses, if any, taking into account IPv4 and IPv6 style
258     * addresses.
259     * @param cm ConnectivityManager
260     * @return the formatted and newline-separated IP addresses, or null if none.
261     */
262    private static String getDefaultIpAddresses(ConnectivityManager cm) {
263        LinkProperties prop = cm.getActiveLinkProperties();
264        return formatIpAddresses(prop);
265    }
266
267    private static String formatIpAddresses(LinkProperties prop) {
268        if (prop == null) return null;
269        Iterator<InetAddress> iter = prop.getAllAddresses().iterator();
270        // If there are no entries, return null
271        if (!iter.hasNext()) return null;
272        // Concatenate all available addresses, comma separated
273        String addresses = "";
274        while (iter.hasNext()) {
275            addresses += iter.next().getHostAddress();
276            if (iter.hasNext()) addresses += "\n";
277        }
278        return addresses;
279    }
280
281    void updateTimes() {
282        mUptime.setSummary(DateUtils.formatDuration(SystemClock.elapsedRealtime()));
283    }
284
285}
286