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