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 */
16package com.android.systemui.statusbar.policy;
17
18import android.content.Context;
19import android.content.Intent;
20import android.database.ContentObserver;
21import android.net.NetworkBadging;
22import android.net.NetworkCapabilities;
23import android.net.NetworkKey;
24import android.net.NetworkScoreManager;
25import android.net.ScoredNetwork;
26import android.net.wifi.WifiManager;
27import android.net.wifi.WifiNetworkScoreCache;
28import android.net.wifi.WifiNetworkScoreCache.CacheListener;
29import android.os.Handler;
30import android.os.Looper;
31import android.os.Message;
32import android.os.Messenger;
33import android.provider.Settings;
34import android.util.Log;
35
36import com.android.internal.annotations.VisibleForTesting;
37import com.android.internal.util.AsyncChannel;
38import com.android.settingslib.Utils;
39import com.android.settingslib.wifi.WifiStatusTracker;
40import com.android.systemui.statusbar.policy.NetworkController.IconState;
41import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
42
43import com.android.systemui.R;
44
45import java.util.Objects;
46import java.util.List;
47
48
49public class WifiSignalController extends
50        SignalController<WifiSignalController.WifiState, SignalController.IconGroup> {
51
52    private final WifiManager mWifiManager;
53    private final AsyncChannel mWifiChannel;
54    private final boolean mHasMobileData;
55    private final NetworkScoreManager mNetworkScoreManager;
56    private final WifiNetworkScoreCache mScoreCache;
57    private final WifiStatusTracker mWifiTracker;
58
59    private boolean mScoringUiEnabled = false;
60
61    public WifiSignalController(Context context, boolean hasMobileData,
62            CallbackHandler callbackHandler, NetworkControllerImpl networkController,
63            NetworkScoreManager networkScoreManager) {
64        super("WifiSignalController", context, NetworkCapabilities.TRANSPORT_WIFI,
65                callbackHandler, networkController);
66        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
67        mWifiTracker = new WifiStatusTracker(mWifiManager);
68        mHasMobileData = hasMobileData;
69        Handler handler = new WifiHandler(Looper.getMainLooper());
70        mWifiChannel = new AsyncChannel();
71        Messenger wifiMessenger = mWifiManager.getWifiServiceMessenger();
72        if (wifiMessenger != null) {
73            mWifiChannel.connect(context, handler, wifiMessenger);
74        }
75        // WiFi only has one state.
76        mCurrentState.iconGroup = mLastState.iconGroup = new IconGroup(
77                "Wi-Fi Icons",
78                WifiIcons.WIFI_SIGNAL_STRENGTH,
79                WifiIcons.QS_WIFI_SIGNAL_STRENGTH,
80                AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH,
81                WifiIcons.WIFI_NO_NETWORK,
82                WifiIcons.QS_WIFI_NO_NETWORK,
83                WifiIcons.WIFI_NO_NETWORK,
84                WifiIcons.QS_WIFI_NO_NETWORK,
85                AccessibilityContentDescriptions.WIFI_NO_CONNECTION
86                );
87
88        mScoreCache = new WifiNetworkScoreCache(context, new CacheListener(handler) {
89            @Override
90            public void networkCacheUpdated(List<ScoredNetwork> networks) {
91                mCurrentState.badgeEnum = getWifiBadgeEnum();
92                notifyListenersIfNecessary();
93            }
94        });
95
96        // Setup scoring
97        mNetworkScoreManager = networkScoreManager;
98        configureScoringGating();
99        registerScoreCache();
100    }
101
102    private void configureScoringGating() {
103        ContentObserver observer = new ContentObserver(new Handler(Looper.getMainLooper())) {
104            @Override
105            public void onChange(boolean selfChange) {
106                mScoringUiEnabled =
107                        Settings.Global.getInt(
108                                mContext.getContentResolver(),
109                                Settings.Global.NETWORK_SCORING_UI_ENABLED, 0) == 1;
110            }
111        };
112        mContext.getContentResolver().registerContentObserver(
113                Settings.Global.getUriFor(Settings.Global.NETWORK_SCORING_UI_ENABLED),
114                false /* notifyForDescendants */,
115                observer);
116
117        observer.onChange(false /* selfChange */); // Set the initial values
118    }
119
120    private void registerScoreCache() {
121        Log.d(mTag, "Registered score cache");
122        mNetworkScoreManager.registerNetworkScoreCache(
123                NetworkKey.TYPE_WIFI,
124                mScoreCache,
125                NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK);
126    }
127
128    @Override
129    protected WifiState cleanState() {
130        return new WifiState();
131    }
132
133    @Override
134    public void notifyListeners(SignalCallback callback) {
135        // only show wifi in the cluster if connected or if wifi-only
136        boolean wifiVisible = mCurrentState.enabled
137                && (mCurrentState.connected || !mHasMobileData);
138        String wifiDesc = wifiVisible ? mCurrentState.ssid : null;
139        boolean ssidPresent = wifiVisible && mCurrentState.ssid != null;
140        String contentDescription = getStringIfExists(getContentDescription());
141        if (mCurrentState.inetCondition == 0) {
142            contentDescription +=
143                    ("," + mContext.getString(R.string.accessibility_quick_settings_no_internet));
144        }
145
146        IconState statusIcon = new IconState(wifiVisible, getCurrentIconId(),
147                Utils.getWifiBadgeResource(mCurrentState.badgeEnum), contentDescription);
148        IconState qsIcon = new IconState(
149                mCurrentState.connected, getQsCurrentIconId(),
150                Utils.getWifiBadgeResource(mCurrentState.badgeEnum), contentDescription);
151        callback.setWifiIndicators(mCurrentState.enabled, statusIcon, qsIcon,
152                ssidPresent && mCurrentState.activityIn, ssidPresent && mCurrentState.activityOut,
153                wifiDesc, mCurrentState.isTransient);
154    }
155
156    @Override
157    public int getCurrentIconId() {
158        if (mCurrentState.badgeEnum != NetworkBadging.BADGING_NONE) {
159            return Utils.WIFI_PIE_FOR_BADGING[mCurrentState.level];
160        }
161        return super.getCurrentIconId();
162    }
163
164    /**
165     * Extract wifi state directly from broadcasts about changes in wifi state.
166     */
167    public void handleBroadcast(Intent intent) {
168        // Update the WifiStatusTracker with the new information and update the score cache.
169        NetworkKey previousNetworkKey = mWifiTracker.networkKey;
170        mWifiTracker.handleBroadcast(intent);
171        updateScoreCacheIfNecessary(previousNetworkKey);
172
173        mCurrentState.isTransient = mWifiTracker.state == WifiManager.WIFI_STATE_ENABLING
174                || mWifiTracker.state == WifiManager.WIFI_AP_STATE_DISABLING
175                || mWifiTracker.connecting;
176        mCurrentState.enabled = mWifiTracker.enabled;
177        mCurrentState.connected = mWifiTracker.connected;
178        mCurrentState.ssid = mWifiTracker.ssid;
179        mCurrentState.rssi = mWifiTracker.rssi;
180        mCurrentState.level = mWifiTracker.level;
181        mCurrentState.badgeEnum = getWifiBadgeEnum();
182        notifyListenersIfNecessary();
183    }
184
185    /**
186     * Clears old scores out of the cache and requests new scores if the network key has changed.
187     *
188     * <p>New scores are requested asynchronously.
189     */
190    private void updateScoreCacheIfNecessary(NetworkKey previousNetworkKey) {
191        if (mWifiTracker.networkKey == null) {
192            return;
193        }
194        if ((previousNetworkKey == null) || !mWifiTracker.networkKey.equals(previousNetworkKey)) {
195            mScoreCache.clearScores();
196            mNetworkScoreManager.requestScores(new NetworkKey[]{mWifiTracker.networkKey});
197        }
198    }
199
200    /**
201     * Returns the wifi badge enum for the current {@link #mWifiTracker} state.
202     *
203     * <p>{@link #updateScoreCacheIfNecessary} should be called prior to this method.
204     */
205    private int getWifiBadgeEnum() {
206        if (!mScoringUiEnabled || mWifiTracker.networkKey == null) {
207            return NetworkBadging.BADGING_NONE;
208        }
209        ScoredNetwork score = mScoreCache.getScoredNetwork(mWifiTracker.networkKey);
210
211        if (score != null) {
212            return score.calculateBadge(mWifiTracker.rssi);
213        }
214        return NetworkBadging.BADGING_NONE;
215    }
216
217    @VisibleForTesting
218    void setActivity(int wifiActivity) {
219        mCurrentState.activityIn = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT
220                || wifiActivity == WifiManager.DATA_ACTIVITY_IN;
221        mCurrentState.activityOut = wifiActivity == WifiManager.DATA_ACTIVITY_INOUT
222                || wifiActivity == WifiManager.DATA_ACTIVITY_OUT;
223        notifyListenersIfNecessary();
224    }
225
226    /**
227     * Handler to receive the data activity on wifi.
228     */
229    private class WifiHandler extends Handler {
230        WifiHandler(Looper looper) {
231            super(looper);
232        }
233
234        @Override
235        public void handleMessage(Message msg) {
236            switch (msg.what) {
237                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
238                    if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
239                        mWifiChannel.sendMessage(Message.obtain(this,
240                                AsyncChannel.CMD_CHANNEL_FULL_CONNECTION));
241                    } else {
242                        Log.e(mTag, "Failed to connect to wifi");
243                    }
244                    break;
245                case WifiManager.DATA_ACTIVITY_NOTIFICATION:
246                    setActivity(msg.arg1);
247                    break;
248                default:
249                    // Ignore
250                    break;
251            }
252        }
253    }
254
255    static class WifiState extends SignalController.State {
256        String ssid;
257        int badgeEnum;
258        boolean isTransient;
259
260        @Override
261        public void copyFrom(State s) {
262            super.copyFrom(s);
263            WifiState state = (WifiState) s;
264            ssid = state.ssid;
265            badgeEnum = state.badgeEnum;
266            isTransient = state.isTransient;
267        }
268
269        @Override
270        protected void toString(StringBuilder builder) {
271            super.toString(builder);
272            builder.append(',').append("ssid=").append(ssid);
273            builder.append(',').append("badgeEnum=").append(badgeEnum);
274            builder.append(',').append("isTransient=").append(isTransient);
275        }
276
277        @Override
278        public boolean equals(Object o) {
279            return super.equals(o)
280                    && Objects.equals(((WifiState) o).ssid, ssid)
281                    && (((WifiState) o).badgeEnum == badgeEnum)
282                    && (((WifiState) o).isTransient == isTransient);
283        }
284    }
285}
286