1 /* 2 * Copyright (C) 2021 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.settings.wifi; 17 18 import static com.android.settingslib.wifi.WifiUtils.getHotspotIconResource; 19 20 import android.annotation.Nullable; 21 import android.content.Context; 22 import android.content.res.Resources; 23 import android.content.res.TypedArray; 24 import android.graphics.drawable.Drawable; 25 import android.graphics.drawable.StateListDrawable; 26 import android.text.TextUtils; 27 import android.view.View; 28 import android.widget.ImageButton; 29 import android.widget.ImageView; 30 import android.widget.TextView; 31 32 import androidx.annotation.DrawableRes; 33 import androidx.annotation.NonNull; 34 import androidx.annotation.VisibleForTesting; 35 import androidx.preference.PreferenceViewHolder; 36 37 import com.android.settingslib.R; 38 import com.android.settingslib.RestrictedPreference; 39 import com.android.settingslib.Utils; 40 import com.android.settingslib.wifi.WifiUtils; 41 import com.android.wifitrackerlib.BaseWifiTracker; 42 import com.android.wifitrackerlib.HotspotNetworkEntry; 43 import com.android.wifitrackerlib.WifiEntry; 44 45 /** 46 * Preference to display a WifiEntry in a wifi picker. 47 */ 48 public class WifiEntryPreference extends RestrictedPreference implements 49 WifiEntry.WifiEntryCallback, 50 View.OnClickListener { 51 52 private static final int[] STATE_SECURED = { 53 R.attr.state_encrypted 54 }; 55 56 private static final int[] FRICTION_ATTRS = { 57 R.attr.wifi_friction 58 }; 59 60 // These values must be kept within [WifiEntry.WIFI_LEVEL_MIN, WifiEntry.WIFI_LEVEL_MAX] 61 private static final int[] WIFI_CONNECTION_STRENGTH = { 62 R.string.accessibility_no_wifi, 63 R.string.accessibility_wifi_one_bar, 64 R.string.accessibility_wifi_two_bars, 65 R.string.accessibility_wifi_three_bars, 66 R.string.accessibility_wifi_signal_full 67 }; 68 69 // StateListDrawable to display secured lock / metered "$" icon 70 @Nullable private final StateListDrawable mFrictionSld; 71 private final WifiUtils.InternetIconInjector mIconInjector; 72 private WifiEntry mWifiEntry; 73 private int mLevel = -1; 74 private boolean mShowX; // Shows the Wi-Fi signl icon of Pie+x when it's true. 75 private CharSequence mContentDescription; 76 private OnButtonClickListener mOnButtonClickListener; 77 WifiEntryPreference(@onNull Context context, @NonNull WifiEntry wifiEntry)78 public WifiEntryPreference(@NonNull Context context, @NonNull WifiEntry wifiEntry) { 79 this(context, wifiEntry, new WifiUtils.InternetIconInjector(context)); 80 } 81 82 @VisibleForTesting WifiEntryPreference(@onNull Context context, @NonNull WifiEntry wifiEntry, @NonNull WifiUtils.InternetIconInjector iconInjector)83 WifiEntryPreference(@NonNull Context context, @NonNull WifiEntry wifiEntry, 84 @NonNull WifiUtils.InternetIconInjector iconInjector) { 85 super(context); 86 87 setLayoutResource(R.layout.preference_access_point); 88 mFrictionSld = getFrictionStateListDrawable(); 89 mWifiEntry = wifiEntry; 90 mWifiEntry.setListener(this); 91 mIconInjector = iconInjector; 92 refresh(); 93 } 94 getWifiEntry()95 public WifiEntry getWifiEntry() { 96 return mWifiEntry; 97 } 98 99 @Override onBindViewHolder(final PreferenceViewHolder view)100 public void onBindViewHolder(final PreferenceViewHolder view) { 101 super.onBindViewHolder(view); 102 if (BaseWifiTracker.isVerboseLoggingEnabled()) { 103 TextView summary = (TextView) view.findViewById(android.R.id.summary); 104 if (summary != null) { 105 summary.setMaxLines(100); 106 } 107 } 108 final Drawable drawable = getIcon(); 109 if (drawable != null) { 110 drawable.setLevel(mLevel); 111 } 112 113 view.itemView.setContentDescription(mContentDescription); 114 115 // Turn off divider 116 view.findViewById(R.id.two_target_divider).setVisibility(View.INVISIBLE); 117 118 // Enable the icon button when the help string in this WifiEntry is not null. 119 final ImageButton imageButton = (ImageButton) view.findViewById(R.id.icon_button); 120 final ImageView frictionImageView = (ImageView) view.findViewById( 121 R.id.friction_icon); 122 if (mWifiEntry.getHelpUriString() != null 123 && mWifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_DISCONNECTED) { 124 final Drawable drawablehelp = getDrawable(R.drawable.ic_help); 125 drawablehelp.setTintList( 126 Utils.getColorAttr(getContext(), android.R.attr.colorControlNormal)); 127 ((ImageView) imageButton).setImageDrawable(drawablehelp); 128 imageButton.setVisibility(View.VISIBLE); 129 imageButton.setOnClickListener(this); 130 imageButton.setContentDescription( 131 getContext().getText(R.string.help_label)); 132 133 if (frictionImageView != null) { 134 frictionImageView.setVisibility(View.GONE); 135 } 136 } else { 137 imageButton.setVisibility(View.GONE); 138 139 if (frictionImageView != null) { 140 frictionImageView.setVisibility(View.VISIBLE); 141 bindFrictionImage(frictionImageView); 142 } 143 } 144 } 145 146 /** 147 * Updates the title and summary; may indirectly call notifyChanged(). 148 */ refresh()149 public void refresh() { 150 setTitle(mWifiEntry.getTitle()); 151 if (mWifiEntry instanceof HotspotNetworkEntry) { 152 updateHotspotIcon(((HotspotNetworkEntry) mWifiEntry).getDeviceType()); 153 } else { 154 int level = mWifiEntry.getLevel(); 155 boolean showX = mWifiEntry.shouldShowXLevelIcon(); 156 157 if (level != mLevel || showX != mShowX) { 158 mLevel = level; 159 mShowX = showX; 160 updateIcon(mShowX, mLevel); 161 } 162 } 163 164 setSummary(mWifiEntry.getSummary(false /* concise */)); 165 mContentDescription = buildContentDescription(); 166 } 167 168 /** 169 * Indicates the state of the WifiEntry has changed and clients may retrieve updates through 170 * the WifiEntry getter methods. 171 */ onUpdated()172 public void onUpdated() { 173 // TODO(b/70983952): Fill this method in 174 refresh(); 175 } 176 177 /** 178 * Result of the connect request indicated by the WifiEntry.CONNECT_STATUS constants. 179 */ onConnectResult(int status)180 public void onConnectResult(int status) { 181 // TODO(b/70983952): Fill this method in 182 } 183 184 /** 185 * Result of the disconnect request indicated by the WifiEntry.DISCONNECT_STATUS constants. 186 */ onDisconnectResult(int status)187 public void onDisconnectResult(int status) { 188 // TODO(b/70983952): Fill this method in 189 } 190 191 /** 192 * Result of the forget request indicated by the WifiEntry.FORGET_STATUS constants. 193 */ onForgetResult(int status)194 public void onForgetResult(int status) { 195 // TODO(b/70983952): Fill this method in 196 } 197 198 /** 199 * Result of the sign-in request indecated by the WifiEntry.SIGNIN_STATUS constants 200 */ onSignInResult(int status)201 public void onSignInResult(int status) { 202 // TODO(b/70983952): Fill this method in 203 } 204 getIconColorAttr()205 protected int getIconColorAttr() { 206 final boolean accent = (mWifiEntry.hasInternetAccess() 207 && mWifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED); 208 return accent ? android.R.attr.colorAccent : android.R.attr.colorControlNormal; 209 } 210 setIconWithTint(Drawable drawable)211 private void setIconWithTint(Drawable drawable) { 212 if (drawable != null) { 213 // Must use Drawable#setTintList() instead of Drawable#setTint() to show the grey 214 // icon when the preference is disabled. 215 drawable.setTintList(Utils.getColorAttr(getContext(), getIconColorAttr())); 216 setIcon(drawable); 217 } else { 218 setIcon(null); 219 } 220 } 221 222 @VisibleForTesting updateIcon(boolean showX, int level)223 void updateIcon(boolean showX, int level) { 224 if (level == -1) { 225 setIcon(null); 226 return; 227 } 228 setIconWithTint(mIconInjector.getIcon(showX, level)); 229 } 230 231 @VisibleForTesting updateHotspotIcon(int deviceType)232 void updateHotspotIcon(int deviceType) { 233 setIconWithTint(getContext().getDrawable(getHotspotIconResource(deviceType))); 234 } 235 236 @Nullable getFrictionStateListDrawable()237 private StateListDrawable getFrictionStateListDrawable() { 238 TypedArray frictionSld; 239 try { 240 frictionSld = getContext().getTheme().obtainStyledAttributes(FRICTION_ATTRS); 241 } catch (Resources.NotFoundException e) { 242 // Fallback for platforms that do not need friction icon resources. 243 frictionSld = null; 244 } 245 return frictionSld != null ? (StateListDrawable) frictionSld.getDrawable(0) : null; 246 } 247 248 /** 249 * Binds the friction icon drawable using a StateListDrawable. 250 * 251 * <p>Friction icons will be rebound when notifyChange() is called, and therefore 252 * do not need to be managed in refresh()</p>. 253 */ bindFrictionImage(ImageView frictionImageView)254 private void bindFrictionImage(ImageView frictionImageView) { 255 if (frictionImageView == null || mFrictionSld == null) { 256 return; 257 } 258 if ((mWifiEntry.getSecurity() != WifiEntry.SECURITY_NONE) 259 && (mWifiEntry.getSecurity() != WifiEntry.SECURITY_OWE)) { 260 mFrictionSld.setState(STATE_SECURED); 261 } 262 frictionImageView.setImageDrawable(mFrictionSld.getCurrent()); 263 } 264 265 /** 266 * Helper method to generate content description string. 267 */ 268 @VisibleForTesting buildContentDescription()269 CharSequence buildContentDescription() { 270 final Context context = getContext(); 271 272 CharSequence contentDescription = getTitle(); 273 final CharSequence summary = getSummary(); 274 if (!TextUtils.isEmpty(summary)) { 275 contentDescription = TextUtils.concat(contentDescription, ",", summary); 276 } 277 int level = mWifiEntry.getLevel(); 278 if (level >= 0 && level < WIFI_CONNECTION_STRENGTH.length) { 279 contentDescription = TextUtils.concat(contentDescription, ",", 280 context.getString(WIFI_CONNECTION_STRENGTH[level])); 281 } 282 return TextUtils.concat(contentDescription, ",", 283 mWifiEntry.getSecurity() == WifiEntry.SECURITY_NONE 284 ? context.getString(R.string.accessibility_wifi_security_type_none) 285 : context.getString(R.string.accessibility_wifi_security_type_secured)); 286 } 287 288 /** 289 * Set listeners, who want to listen the button client event. 290 */ setOnButtonClickListener(OnButtonClickListener listener)291 public void setOnButtonClickListener(OnButtonClickListener listener) { 292 mOnButtonClickListener = listener; 293 notifyChanged(); 294 } 295 296 @Override getSecondTargetResId()297 protected int getSecondTargetResId() { 298 return R.layout.access_point_friction_widget; 299 } 300 301 @Override onClick(View view)302 public void onClick(View view) { 303 if (view.getId() == R.id.icon_button) { 304 if (mOnButtonClickListener != null) { 305 mOnButtonClickListener.onButtonClick(this); 306 } 307 } 308 } 309 310 /** 311 * Callback to inform the caller that the icon button is clicked. 312 */ 313 public interface OnButtonClickListener { 314 315 /** 316 * Register to listen the button click event. 317 */ onButtonClick(WifiEntryPreference preference)318 void onButtonClick(WifiEntryPreference preference); 319 } 320 getDrawable(@rawableRes int iconResId)321 private Drawable getDrawable(@DrawableRes int iconResId) { 322 Drawable buttonIcon = null; 323 324 try { 325 buttonIcon = getContext().getDrawable(iconResId); 326 } catch (Resources.NotFoundException exception) { 327 // Do nothing 328 } 329 return buttonIcon; 330 } 331 } 332