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