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