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 package com.android.systemui.statusbar.connectivity; 17 18 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; 19 import static android.net.NetworkCapabilities.TRANSPORT_WIFI; 20 import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN; 21 import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT; 22 import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT; 23 24 import android.content.Context; 25 import android.content.Intent; 26 import android.net.wifi.WifiManager; 27 import android.os.Handler; 28 import android.text.Html; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 import com.android.internal.util.Preconditions; 32 import com.android.settingslib.SignalIcon.IconGroup; 33 import com.android.settingslib.SignalIcon.MobileIconGroup; 34 import com.android.settingslib.graph.SignalDrawable; 35 import com.android.settingslib.mobile.TelephonyIcons; 36 import com.android.settingslib.wifi.WifiStatusTracker; 37 import com.android.systemui.R; 38 import com.android.systemui.dagger.qualifiers.Background; 39 40 import java.io.PrintWriter; 41 import java.util.BitSet; 42 43 /** */ 44 public class WifiSignalController extends SignalController<WifiState, IconGroup> { 45 private final boolean mHasMobileDataFeature; 46 private final WifiStatusTracker mWifiTracker; 47 private final IconGroup mUnmergedWifiIconGroup = WifiIcons.UNMERGED_WIFI; 48 private final MobileIconGroup mCarrierMergedWifiIconGroup = TelephonyIcons.CARRIER_MERGED_WIFI; 49 private final WifiManager mWifiManager; 50 51 private final Handler mBgHandler; 52 WifiSignalController( Context context, boolean hasMobileDataFeature, CallbackHandler callbackHandler, NetworkControllerImpl networkController, WifiManager wifiManager, WifiStatusTrackerFactory trackerFactory, @Background Handler bgHandler)53 public WifiSignalController( 54 Context context, 55 boolean hasMobileDataFeature, 56 CallbackHandler callbackHandler, 57 NetworkControllerImpl networkController, 58 WifiManager wifiManager, 59 WifiStatusTrackerFactory trackerFactory, 60 @Background Handler bgHandler) { 61 super( 62 "WifiSignalController", 63 context, 64 TRANSPORT_WIFI, 65 callbackHandler, 66 networkController); 67 mBgHandler = bgHandler; 68 mWifiManager = wifiManager; 69 mWifiTracker = trackerFactory.createTracker(this::handleStatusUpdated, bgHandler); 70 mWifiTracker.setListening(true); 71 mHasMobileDataFeature = hasMobileDataFeature; 72 if (wifiManager != null) { 73 wifiManager.registerTrafficStateCallback(context.getMainExecutor(), 74 new WifiTrafficStateCallback()); 75 } 76 mCurrentState.iconGroup = mLastState.iconGroup = mUnmergedWifiIconGroup; 77 } 78 79 @Override cleanState()80 protected WifiState cleanState() { 81 return new WifiState(); 82 } 83 refreshLocale()84 void refreshLocale() { 85 mWifiTracker.refreshLocale(); 86 } 87 88 @Override notifyListeners(SignalCallback callback)89 public void notifyListeners(SignalCallback callback) { 90 if (mCurrentState.isCarrierMerged) { 91 if (mCurrentState.isDefault || !mNetworkController.isRadioOn()) { 92 notifyListenersForCarrierWifi(callback); 93 } 94 } else { 95 notifyListenersForNonCarrierWifi(callback); 96 } 97 } 98 notifyListenersForNonCarrierWifi(SignalCallback callback)99 private void notifyListenersForNonCarrierWifi(SignalCallback callback) { 100 // only show wifi in the cluster if connected or if wifi-only 101 boolean visibleWhenEnabled = mContext.getResources().getBoolean( 102 R.bool.config_showWifiIndicatorWhenEnabled); 103 boolean wifiVisible = mCurrentState.enabled && ( 104 (mCurrentState.connected && mCurrentState.inetCondition == 1) 105 || !mHasMobileDataFeature || mCurrentState.isDefault 106 || visibleWhenEnabled); 107 String wifiDesc = mCurrentState.connected ? mCurrentState.ssid : null; 108 boolean ssidPresent = wifiVisible && mCurrentState.ssid != null; 109 String contentDescription = getTextIfExists(getContentDescription()).toString(); 110 if (mCurrentState.inetCondition == 0) { 111 contentDescription += ("," + mContext.getString(R.string.data_connection_no_internet)); 112 } 113 IconState statusIcon = new IconState( 114 wifiVisible, getCurrentIconId(), contentDescription); 115 IconState qsIcon = null; 116 if (mCurrentState.isDefault || (!mNetworkController.isRadioOn() 117 && !mNetworkController.isEthernetDefault())) { 118 qsIcon = new IconState(mCurrentState.connected, 119 mWifiTracker.isCaptivePortal ? R.drawable.ic_qs_wifi_disconnected 120 : getQsCurrentIconId(), contentDescription); 121 } 122 WifiIndicators wifiIndicators = new WifiIndicators( 123 mCurrentState.enabled, statusIcon, qsIcon, 124 ssidPresent && mCurrentState.activityIn, 125 ssidPresent && mCurrentState.activityOut, 126 wifiDesc, mCurrentState.isTransient, mCurrentState.statusLabel 127 ); 128 callback.setWifiIndicators(wifiIndicators); 129 } 130 notifyListenersForCarrierWifi(SignalCallback callback)131 private void notifyListenersForCarrierWifi(SignalCallback callback) { 132 MobileIconGroup icons = mCarrierMergedWifiIconGroup; 133 String contentDescription = getTextIfExists(getContentDescription()).toString(); 134 CharSequence dataContentDescriptionHtml = getTextIfExists(icons.dataContentDescription); 135 136 CharSequence dataContentDescription = Html.fromHtml( 137 dataContentDescriptionHtml.toString(), 0).toString(); 138 if (mCurrentState.inetCondition == 0) { 139 dataContentDescription = mContext.getString(R.string.data_connection_no_internet); 140 } 141 boolean sbVisible = mCurrentState.enabled && mCurrentState.connected 142 && mCurrentState.isDefault; 143 IconState statusIcon = 144 new IconState(sbVisible, getCurrentIconIdForCarrierWifi(), contentDescription); 145 int typeIcon = sbVisible ? icons.dataType : 0; 146 int qsTypeIcon = 0; 147 IconState qsIcon = null; 148 if (sbVisible) { 149 qsTypeIcon = icons.dataType; 150 qsIcon = new IconState(mCurrentState.connected, getQsCurrentIconIdForCarrierWifi(), 151 contentDescription); 152 } 153 CharSequence description = 154 mNetworkController.getNetworkNameForCarrierWiFi(mCurrentState.subId); 155 MobileDataIndicators mobileDataIndicators = new MobileDataIndicators( 156 statusIcon, qsIcon, typeIcon, qsTypeIcon, 157 mCurrentState.activityIn, mCurrentState.activityOut, dataContentDescription, 158 dataContentDescriptionHtml, description, 159 mCurrentState.subId, /* roaming= */ false, /* showTriangle= */ true 160 ); 161 callback.setMobileDataIndicators(mobileDataIndicators); 162 } 163 getCurrentIconIdForCarrierWifi()164 private int getCurrentIconIdForCarrierWifi() { 165 int level = mCurrentState.level; 166 // The WiFi signal level returned by WifiManager#calculateSignalLevel start from 0, so 167 // WifiManager#getMaxSignalLevel + 1 represents the total level buckets count. 168 int totalLevel = mWifiManager.getMaxSignalLevel() + 1; 169 // A carrier merged connection could come from a WIFI *or* CELLULAR transport, so we can't 170 // use [mCurrentState.inetCondition], which only checks the WIFI status. Instead, check if 171 // the default connection is validated at all. 172 boolean noInternet = !mCurrentState.isDefaultConnectionValidated; 173 if (mCurrentState.connected) { 174 return SignalDrawable.getState(level, totalLevel, noInternet); 175 } else if (mCurrentState.enabled) { 176 return SignalDrawable.getEmptyState(totalLevel); 177 } else { 178 return 0; 179 } 180 } 181 getQsCurrentIconIdForCarrierWifi()182 private int getQsCurrentIconIdForCarrierWifi() { 183 return getCurrentIconIdForCarrierWifi(); 184 } 185 186 /** 187 * Fetches wifi initial state replacing the initial sticky broadcast. 188 */ fetchInitialState()189 public void fetchInitialState() { 190 doInBackground(() -> { 191 mWifiTracker.fetchInitialState(); 192 copyWifiStates(); 193 notifyListenersIfNecessary(); 194 }); 195 } 196 197 /** 198 * Extract wifi state directly from broadcasts about changes in wifi state. 199 */ handleBroadcast(Intent intent)200 void handleBroadcast(Intent intent) { 201 doInBackground(() -> { 202 mWifiTracker.handleBroadcast(intent); 203 copyWifiStates(); 204 notifyListenersIfNecessary(); 205 }); 206 } 207 handleStatusUpdated()208 private void handleStatusUpdated() { 209 // The WifiStatusTracker callback comes in on the main thread, but the rest of our data 210 // access happens on the bgHandler 211 doInBackground(() -> { 212 copyWifiStates(); 213 notifyListenersIfNecessary(); 214 }); 215 } 216 doInBackground(Runnable action)217 private void doInBackground(Runnable action) { 218 if (Thread.currentThread() != mBgHandler.getLooper().getThread()) { 219 mBgHandler.post(action); 220 } else { 221 action.run(); 222 } 223 } 224 copyWifiStates()225 private void copyWifiStates() { 226 // Data access should only happen on our bg thread 227 Preconditions.checkState(mBgHandler.getLooper().isCurrentThread()); 228 229 mCurrentState.enabled = mWifiTracker.enabled; 230 mCurrentState.isDefault = mWifiTracker.isDefaultNetwork; 231 mCurrentState.connected = mWifiTracker.connected; 232 mCurrentState.ssid = mWifiTracker.ssid; 233 mCurrentState.rssi = mWifiTracker.rssi; 234 mCurrentState.level = mWifiTracker.level; 235 mCurrentState.statusLabel = mWifiTracker.statusLabel; 236 mCurrentState.isCarrierMerged = mWifiTracker.isCarrierMerged; 237 mCurrentState.subId = mWifiTracker.subId; 238 mCurrentState.iconGroup = 239 mCurrentState.isCarrierMerged ? mCarrierMergedWifiIconGroup 240 : mUnmergedWifiIconGroup; 241 } 242 isCarrierMergedWifi(int subId)243 boolean isCarrierMergedWifi(int subId) { 244 return mCurrentState.isDefault 245 && mCurrentState.isCarrierMerged && (mCurrentState.subId == subId); 246 } 247 248 @Override updateConnectivity(BitSet connectedTransports, BitSet validatedTransports)249 void updateConnectivity(BitSet connectedTransports, BitSet validatedTransports) { 250 mCurrentState.inetCondition = validatedTransports.get(mTransportType) ? 1 : 0; 251 // Because a carrier merged connection can come from either a CELLULAR *or* WIFI transport, 252 // we need to also store if either transport is validated to correctly display the carrier 253 // merged case. 254 mCurrentState.isDefaultConnectionValidated = 255 validatedTransports.get(TRANSPORT_CELLULAR) 256 || validatedTransports.get(TRANSPORT_WIFI); 257 notifyListenersIfNecessary(); 258 } 259 260 @VisibleForTesting setActivity(int wifiActivity)261 void setActivity(int wifiActivity) { 262 mCurrentState.activityIn = wifiActivity == DATA_ACTIVITY_INOUT 263 || wifiActivity == DATA_ACTIVITY_IN; 264 mCurrentState.activityOut = wifiActivity == DATA_ACTIVITY_INOUT 265 || wifiActivity == DATA_ACTIVITY_OUT; 266 notifyListenersIfNecessary(); 267 } 268 269 @Override dump(PrintWriter pw)270 public void dump(PrintWriter pw) { 271 super.dump(pw); 272 mWifiTracker.dump(pw); 273 dumpTableData(pw); 274 } 275 276 /** 277 * Handler to receive the data activity on wifi. 278 */ 279 private class WifiTrafficStateCallback implements WifiManager.TrafficStateCallback { 280 @Override onStateChanged(int state)281 public void onStateChanged(int state) { 282 setActivity(state); 283 } 284 } 285 } 286