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.systemui.qs.tiles; 18 19import android.content.Context; 20import android.content.Intent; 21import android.content.pm.PackageManager; 22import android.content.res.Resources; 23import android.provider.Settings; 24import android.service.quicksettings.Tile; 25import android.util.Log; 26import android.view.View; 27import android.view.ViewGroup; 28import android.widget.Switch; 29 30import com.android.internal.logging.MetricsLogger; 31import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 32import com.android.settingslib.wifi.AccessPoint; 33import com.android.systemui.Dependency; 34import com.android.systemui.R; 35import com.android.systemui.plugins.ActivityStarter; 36import com.android.systemui.plugins.qs.DetailAdapter; 37import com.android.systemui.plugins.qs.QSIconView; 38import com.android.systemui.plugins.qs.QSTile; 39import com.android.systemui.plugins.qs.QSTile.SignalState; 40import com.android.systemui.qs.AlphaControlledSignalTileView; 41import com.android.systemui.qs.QSDetailItems; 42import com.android.systemui.qs.QSDetailItems.Item; 43import com.android.systemui.qs.QSHost; 44import com.android.systemui.qs.tileimpl.QSIconViewImpl; 45import com.android.systemui.qs.tileimpl.QSTileImpl; 46import com.android.systemui.statusbar.policy.NetworkController; 47import com.android.systemui.statusbar.policy.NetworkController.AccessPointController; 48import com.android.systemui.statusbar.policy.NetworkController.IconState; 49import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; 50 51import java.util.List; 52 53/** Quick settings tile: Wifi **/ 54public class WifiTile extends QSTileImpl<SignalState> { 55 private static final Intent WIFI_SETTINGS = new Intent(Settings.ACTION_WIFI_SETTINGS); 56 57 protected final NetworkController mController; 58 private final AccessPointController mWifiController; 59 private final WifiDetailAdapter mDetailAdapter; 60 private final QSTile.SignalState mStateBeforeClick = newTileState(); 61 62 protected final WifiSignalCallback mSignalCallback = new WifiSignalCallback(); 63 private final ActivityStarter mActivityStarter; 64 private boolean mExpectDisabled; 65 66 public WifiTile(QSHost host) { 67 super(host); 68 mController = Dependency.get(NetworkController.class); 69 mWifiController = mController.getAccessPointController(); 70 mDetailAdapter = (WifiDetailAdapter) createDetailAdapter(); 71 mActivityStarter = Dependency.get(ActivityStarter.class); 72 } 73 74 @Override 75 public SignalState newTileState() { 76 return new SignalState(); 77 } 78 79 @Override 80 public void handleSetListening(boolean listening) { 81 if (listening) { 82 mController.addCallback(mSignalCallback); 83 } else { 84 mController.removeCallback(mSignalCallback); 85 } 86 } 87 88 @Override 89 public void setDetailListening(boolean listening) { 90 if (listening) { 91 mWifiController.addAccessPointCallback(mDetailAdapter); 92 } else { 93 mWifiController.removeAccessPointCallback(mDetailAdapter); 94 } 95 } 96 97 @Override 98 public DetailAdapter getDetailAdapter() { 99 return mDetailAdapter; 100 } 101 102 @Override 103 protected DetailAdapter createDetailAdapter() { 104 return new WifiDetailAdapter(); 105 } 106 107 @Override 108 public QSIconView createTileView(Context context) { 109 return new AlphaControlledSignalTileView(context); 110 } 111 112 @Override 113 public Intent getLongClickIntent() { 114 return WIFI_SETTINGS; 115 } 116 117 @Override 118 protected void handleClick() { 119 // Secondary clicks are header clicks, just toggle. 120 mState.copyTo(mStateBeforeClick); 121 boolean wifiEnabled = mState.value; 122 // Immediately enter transient state when turning on wifi. 123 refreshState(wifiEnabled ? null : ARG_SHOW_TRANSIENT_ENABLING); 124 mController.setWifiEnabled(!wifiEnabled); 125 mExpectDisabled = wifiEnabled; 126 if (mExpectDisabled) { 127 mHandler.postDelayed(() -> { 128 if (mExpectDisabled) { 129 mExpectDisabled = false; 130 refreshState(); 131 } 132 }, QSIconViewImpl.QS_ANIM_LENGTH); 133 } 134 } 135 136 @Override 137 protected void handleSecondaryClick() { 138 if (!mWifiController.canConfigWifi()) { 139 mActivityStarter.postStartActivityDismissingKeyguard( 140 new Intent(Settings.ACTION_WIFI_SETTINGS), 0); 141 return; 142 } 143 showDetail(true); 144 if (!mState.value) { 145 mController.setWifiEnabled(true); 146 } 147 } 148 149 @Override 150 public CharSequence getTileLabel() { 151 return mContext.getString(R.string.quick_settings_wifi_label); 152 } 153 154 @Override 155 protected void handleUpdateState(SignalState state, Object arg) { 156 if (DEBUG) Log.d(TAG, "handleUpdateState arg=" + arg); 157 final CallbackInfo cb = mSignalCallback.mInfo; 158 if (mExpectDisabled) { 159 if (cb.enabled) { 160 return; // Ignore updates until disabled event occurs. 161 } else { 162 mExpectDisabled = false; 163 } 164 } 165 boolean transientEnabling = arg == ARG_SHOW_TRANSIENT_ENABLING; 166 boolean wifiConnected = cb.enabled && (cb.wifiSignalIconId > 0) && (cb.ssid != null); 167 boolean wifiNotConnected = (cb.wifiSignalIconId > 0) && (cb.ssid == null); 168 boolean enabledChanging = state.value != cb.enabled; 169 if (enabledChanging) { 170 mDetailAdapter.setItemsVisible(cb.enabled); 171 fireToggleStateChanged(cb.enabled); 172 } 173 if (state.slash == null) { 174 state.slash = new SlashState(); 175 state.slash.rotation = 6; 176 } 177 state.slash.isSlashed = false; 178 boolean isTransient = transientEnabling || cb.isTransient; 179 state.secondaryLabel = getSecondaryLabel(isTransient, cb.statusLabel); 180 state.state = Tile.STATE_ACTIVE; 181 state.dualTarget = true; 182 state.value = transientEnabling || cb.enabled; 183 state.activityIn = cb.enabled && cb.activityIn; 184 state.activityOut = cb.enabled && cb.activityOut; 185 final StringBuffer minimalContentDescription = new StringBuffer(); 186 final Resources r = mContext.getResources(); 187 if (isTransient) { 188 state.icon = ResourceIcon.get(R.drawable.ic_signal_wifi_transient_animation); 189 state.label = r.getString(R.string.quick_settings_wifi_label); 190 } else if (!state.value) { 191 state.slash.isSlashed = true; 192 state.state = Tile.STATE_INACTIVE; 193 state.icon = ResourceIcon.get(R.drawable.ic_qs_wifi_disabled); 194 state.label = r.getString(R.string.quick_settings_wifi_label); 195 } else if (wifiConnected) { 196 state.icon = ResourceIcon.get(cb.wifiSignalIconId); 197 state.label = removeDoubleQuotes(cb.ssid); 198 } else if (wifiNotConnected) { 199 state.icon = ResourceIcon.get(R.drawable.ic_qs_wifi_disconnected); 200 state.label = r.getString(R.string.quick_settings_wifi_label); 201 } else { 202 state.icon = ResourceIcon.get(R.drawable.ic_qs_wifi_no_network); 203 state.label = r.getString(R.string.quick_settings_wifi_label); 204 } 205 minimalContentDescription.append( 206 mContext.getString(R.string.quick_settings_wifi_label)).append(","); 207 if (state.value) { 208 if (wifiConnected) { 209 minimalContentDescription.append(cb.wifiSignalContentDescription).append(","); 210 minimalContentDescription.append(removeDoubleQuotes(cb.ssid)); 211 } 212 } 213 state.contentDescription = minimalContentDescription.toString(); 214 state.dualLabelContentDescription = r.getString( 215 R.string.accessibility_quick_settings_open_settings, getTileLabel()); 216 state.expandedAccessibilityClassName = Switch.class.getName(); 217 } 218 219 private CharSequence getSecondaryLabel(boolean isTransient, String statusLabel) { 220 return isTransient 221 ? mContext.getString(R.string.quick_settings_wifi_secondary_label_transient) 222 : statusLabel; 223 } 224 225 @Override 226 public int getMetricsCategory() { 227 return MetricsEvent.QS_WIFI; 228 } 229 230 @Override 231 protected boolean shouldAnnouncementBeDelayed() { 232 return mStateBeforeClick.value == mState.value; 233 } 234 235 @Override 236 protected String composeChangeAnnouncement() { 237 if (mState.value) { 238 return mContext.getString(R.string.accessibility_quick_settings_wifi_changed_on); 239 } else { 240 return mContext.getString(R.string.accessibility_quick_settings_wifi_changed_off); 241 } 242 } 243 244 @Override 245 public boolean isAvailable() { 246 return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI); 247 } 248 249 private static String removeDoubleQuotes(String string) { 250 if (string == null) return null; 251 final int length = string.length(); 252 if ((length > 1) && (string.charAt(0) == '"') && (string.charAt(length - 1) == '"')) { 253 return string.substring(1, length - 1); 254 } 255 return string; 256 } 257 258 protected static final class CallbackInfo { 259 boolean enabled; 260 boolean connected; 261 int wifiSignalIconId; 262 String ssid; 263 boolean activityIn; 264 boolean activityOut; 265 String wifiSignalContentDescription; 266 boolean isTransient; 267 public String statusLabel; 268 269 @Override 270 public String toString() { 271 return new StringBuilder("CallbackInfo[") 272 .append("enabled=").append(enabled) 273 .append(",connected=").append(connected) 274 .append(",wifiSignalIconId=").append(wifiSignalIconId) 275 .append(",ssid=").append(ssid) 276 .append(",activityIn=").append(activityIn) 277 .append(",activityOut=").append(activityOut) 278 .append(",wifiSignalContentDescription=").append(wifiSignalContentDescription) 279 .append(",isTransient=").append(isTransient) 280 .append(']').toString(); 281 } 282 } 283 284 protected final class WifiSignalCallback implements SignalCallback { 285 final CallbackInfo mInfo = new CallbackInfo(); 286 287 @Override 288 public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon, 289 boolean activityIn, boolean activityOut, String description, boolean isTransient, 290 String statusLabel) { 291 if (DEBUG) Log.d(TAG, "onWifiSignalChanged enabled=" + enabled); 292 mInfo.enabled = enabled; 293 mInfo.connected = qsIcon.visible; 294 mInfo.wifiSignalIconId = qsIcon.icon; 295 mInfo.ssid = description; 296 mInfo.activityIn = activityIn; 297 mInfo.activityOut = activityOut; 298 mInfo.wifiSignalContentDescription = qsIcon.contentDescription; 299 mInfo.isTransient = isTransient; 300 mInfo.statusLabel = statusLabel; 301 if (isShowingDetail()) { 302 mDetailAdapter.updateItems(); 303 } 304 refreshState(); 305 } 306 } 307 308 protected class WifiDetailAdapter implements DetailAdapter, 309 NetworkController.AccessPointController.AccessPointCallback, QSDetailItems.Callback { 310 311 private QSDetailItems mItems; 312 private AccessPoint[] mAccessPoints; 313 314 @Override 315 public CharSequence getTitle() { 316 return mContext.getString(R.string.quick_settings_wifi_label); 317 } 318 319 public Intent getSettingsIntent() { 320 return WIFI_SETTINGS; 321 } 322 323 @Override 324 public Boolean getToggleState() { 325 return mState.value; 326 } 327 328 @Override 329 public void setToggleState(boolean state) { 330 if (DEBUG) Log.d(TAG, "setToggleState " + state); 331 MetricsLogger.action(mContext, MetricsEvent.QS_WIFI_TOGGLE, state); 332 mController.setWifiEnabled(state); 333 } 334 335 @Override 336 public int getMetricsCategory() { 337 return MetricsEvent.QS_WIFI_DETAILS; 338 } 339 340 @Override 341 public View createDetailView(Context context, View convertView, ViewGroup parent) { 342 if (DEBUG) Log.d(TAG, "createDetailView convertView=" + (convertView != null)); 343 mAccessPoints = null; 344 mItems = QSDetailItems.convertOrInflate(context, convertView, parent); 345 mItems.setTagSuffix("Wifi"); 346 mItems.setCallback(this); 347 mWifiController.scanForAccessPoints(); // updates APs and items 348 setItemsVisible(mState.value); 349 return mItems; 350 } 351 352 @Override 353 public void onAccessPointsChanged(final List<AccessPoint> accessPoints) { 354 mAccessPoints = accessPoints.toArray(new AccessPoint[accessPoints.size()]); 355 filterUnreachableAPs(); 356 357 updateItems(); 358 } 359 360 /** Filter unreachable APs from mAccessPoints */ 361 private void filterUnreachableAPs() { 362 int numReachable = 0; 363 for (AccessPoint ap : mAccessPoints) { 364 if (ap.isReachable()) numReachable++; 365 } 366 if (numReachable != mAccessPoints.length) { 367 AccessPoint[] unfiltered = mAccessPoints; 368 mAccessPoints = new AccessPoint[numReachable]; 369 int i = 0; 370 for (AccessPoint ap : unfiltered) { 371 if (ap.isReachable()) mAccessPoints[i++] = ap; 372 } 373 } 374 } 375 376 @Override 377 public void onSettingsActivityTriggered(Intent settingsIntent) { 378 mActivityStarter.postStartActivityDismissingKeyguard(settingsIntent, 0); 379 } 380 381 @Override 382 public void onDetailItemClick(Item item) { 383 if (item == null || item.tag == null) return; 384 final AccessPoint ap = (AccessPoint) item.tag; 385 if (!ap.isActive()) { 386 if (mWifiController.connect(ap)) { 387 mHost.collapsePanels(); 388 } 389 } 390 showDetail(false); 391 } 392 393 @Override 394 public void onDetailItemDisconnect(Item item) { 395 // noop 396 } 397 398 public void setItemsVisible(boolean visible) { 399 if (mItems == null) return; 400 mItems.setItemsVisible(visible); 401 } 402 403 private void updateItems() { 404 if (mItems == null) return; 405 if ((mAccessPoints != null && mAccessPoints.length > 0) 406 || !mSignalCallback.mInfo.enabled) { 407 fireScanStateChanged(false); 408 } else { 409 fireScanStateChanged(true); 410 } 411 412 // Wi-Fi is off 413 if (!mSignalCallback.mInfo.enabled) { 414 mItems.setEmptyState(R.drawable.ic_qs_wifi_detail_empty, 415 R.string.wifi_is_off); 416 mItems.setItems(null); 417 return; 418 } 419 420 // No available access points 421 mItems.setEmptyState(R.drawable.ic_qs_wifi_detail_empty, 422 R.string.quick_settings_wifi_detail_empty_text); 423 424 // Build the list 425 Item[] items = null; 426 if (mAccessPoints != null) { 427 items = new Item[mAccessPoints.length]; 428 for (int i = 0; i < mAccessPoints.length; i++) { 429 final AccessPoint ap = mAccessPoints[i]; 430 final Item item = new Item(); 431 item.tag = ap; 432 item.iconResId = mWifiController.getIcon(ap); 433 item.line1 = ap.getSsid(); 434 item.line2 = ap.isActive() ? ap.getSummary() : null; 435 item.icon2 = ap.getSecurity() != AccessPoint.SECURITY_NONE 436 ? R.drawable.qs_ic_wifi_lock 437 : -1; 438 items[i] = item; 439 } 440 } 441 mItems.setItems(items); 442 } 443 } 444} 445