1 package com.android.tv.settings.widget; 2 3 import android.content.Context; 4 import android.content.pm.PackageManager; 5 import android.content.res.ColorStateList; 6 import android.content.res.Resources; 7 import android.content.res.TypedArray; 8 import android.graphics.drawable.Drawable; 9 import android.graphics.drawable.StateListDrawable; 10 import android.net.wifi.WifiConfiguration; 11 import android.os.Looper; 12 import android.os.UserHandle; 13 import android.text.TextUtils; 14 import android.util.AttributeSet; 15 import android.util.SparseArray; 16 import android.view.View; 17 import android.widget.ImageView; 18 import android.widget.TextView; 19 20 import androidx.annotation.Nullable; 21 import androidx.annotation.VisibleForTesting; 22 import androidx.preference.Preference; 23 import androidx.preference.PreferenceViewHolder; 24 25 import com.android.tv.settings.R; 26 import com.android.settingslib.wifi.AccessPoint; 27 import com.android.settingslib.wifi.AccessPoint.Speed; 28 29 public class AccessPointPreference extends Preference { 30 31 private static final int[] STATE_SECURED = { 32 R.attr.state_encrypted 33 }; 34 35 private static final int[] STATE_METERED = { 36 R.attr.state_metered 37 }; 38 39 private static final int[] FRICTION_ATTRS = { 40 R.attr.wifi_friction 41 }; 42 43 private static final int[] WIFI_CONNECTION_STRENGTH = { 44 R.string.accessibility_no_wifi, 45 R.string.accessibility_wifi_one_bar, 46 R.string.accessibility_wifi_two_bars, 47 R.string.accessibility_wifi_three_bars, 48 R.string.accessibility_wifi_signal_full 49 }; 50 51 @Nullable 52 private final StateListDrawable mFrictionSld; 53 private final int mBadgePadding; 54 private final UserBadgeCache mBadgeCache; 55 private final IconInjector mIconInjector; 56 private TextView mTitleView; 57 private boolean mShowDivider; 58 59 private boolean mForSavedNetworks = false; 60 private AccessPoint mAccessPoint; 61 private int mLevel; 62 private CharSequence mContentDescription; 63 private int mDefaultIconResId; 64 private int mWifiSpeed = Speed.NONE; 65 66 @Nullable getFrictionStateListDrawable(Context context)67 private static StateListDrawable getFrictionStateListDrawable(Context context) { 68 TypedArray frictionSld; 69 try { 70 frictionSld = context.getTheme().obtainStyledAttributes(FRICTION_ATTRS); 71 } catch (Resources.NotFoundException e) { 72 // Fallback for platforms that do not need friction icon resources. 73 frictionSld = null; 74 } 75 return frictionSld != null ? (StateListDrawable) frictionSld.getDrawable(0) : null; 76 } 77 78 // Used for fake pref. AccessPointPreference(Context context, AttributeSet attrs)79 public AccessPointPreference(Context context, AttributeSet attrs) { 80 super(context, attrs); 81 mFrictionSld = null; 82 mBadgePadding = 0; 83 mBadgeCache = null; 84 mIconInjector = new IconInjector(context); 85 } 86 AccessPointPreference(AccessPoint accessPoint, Context context, UserBadgeCache cache, boolean forSavedNetworks)87 public AccessPointPreference(AccessPoint accessPoint, Context context, UserBadgeCache cache, 88 boolean forSavedNetworks) { 89 this(accessPoint, context, cache, 0 /* iconResId */, forSavedNetworks); 90 refresh(); 91 } 92 AccessPointPreference(AccessPoint accessPoint, Context context, UserBadgeCache cache, int iconResId, boolean forSavedNetworks)93 public AccessPointPreference(AccessPoint accessPoint, Context context, UserBadgeCache cache, 94 int iconResId, boolean forSavedNetworks) { 95 this(accessPoint, context, cache, iconResId, forSavedNetworks, 96 getFrictionStateListDrawable(context), -1 /* level */, new IconInjector(context)); 97 } 98 99 @VisibleForTesting AccessPointPreference(AccessPoint accessPoint, Context context, UserBadgeCache cache, int iconResId, boolean forSavedNetworks, StateListDrawable frictionSld, int level, IconInjector iconInjector)100 AccessPointPreference(AccessPoint accessPoint, Context context, UserBadgeCache cache, 101 int iconResId, boolean forSavedNetworks, StateListDrawable frictionSld, 102 int level, IconInjector iconInjector) { 103 super(context); 104 setLayoutResource(R.layout.preference_access_point); 105 setWidgetLayoutResource(getWidgetLayoutResourceId()); 106 mBadgeCache = cache; 107 mAccessPoint = accessPoint; 108 mForSavedNetworks = forSavedNetworks; 109 mAccessPoint.setTag(this); 110 mLevel = level; 111 mDefaultIconResId = iconResId; 112 mFrictionSld = frictionSld; 113 mIconInjector = iconInjector; 114 mBadgePadding = context.getResources() 115 .getDimensionPixelSize(R.dimen.wifi_preference_badge_padding); 116 } 117 getWidgetLayoutResourceId()118 protected int getWidgetLayoutResourceId() { 119 return R.layout.access_point_friction_widget; 120 } 121 getAccessPoint()122 public AccessPoint getAccessPoint() { 123 return mAccessPoint; 124 } 125 126 @Override onBindViewHolder(final PreferenceViewHolder view)127 public void onBindViewHolder(final PreferenceViewHolder view) { 128 super.onBindViewHolder(view); 129 if (mAccessPoint == null) { 130 // Used for fake pref. 131 return; 132 } 133 Drawable drawable = getIcon(); 134 if (drawable != null) { 135 drawable.setLevel(mLevel); 136 } 137 138 mTitleView = (TextView) view.findViewById(android.R.id.title); 139 if (mTitleView != null) { 140 // Attach to the end of the title view 141 mTitleView.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, null, null); 142 mTitleView.setCompoundDrawablePadding(mBadgePadding); 143 } 144 view.itemView.setContentDescription(mContentDescription); 145 146 ImageView frictionImageView = (ImageView) view.findViewById(R.id.friction_icon); 147 bindFrictionImage(frictionImageView); 148 149 final View divider = view.findViewById(R.id.two_target_divider); 150 divider.setVisibility(shouldShowDivider() ? View.VISIBLE : View.INVISIBLE); 151 } 152 shouldShowDivider()153 public boolean shouldShowDivider() { 154 return mShowDivider; 155 } 156 setShowDivider(boolean showDivider)157 public void setShowDivider(boolean showDivider) { 158 mShowDivider = showDivider; 159 notifyChanged(); 160 } 161 updateIcon(int level, Context context)162 protected void updateIcon(int level, Context context) { 163 if (level == -1) { 164 safeSetDefaultIcon(); 165 return; 166 } 167 168 Drawable drawable = mIconInjector.getIcon(level); 169 if (!mForSavedNetworks && drawable != null) { 170 drawable.setTintList(getColorAttr(context, android.R.attr.colorControlNormal)); 171 setIcon(drawable); 172 } else { 173 safeSetDefaultIcon(); 174 } 175 } 176 177 /** 178 * Binds the friction icon drawable using a StateListDrawable. 179 * 180 * <p>Friction icons will be rebound when notifyChange() is called, and therefore 181 * do not need to be managed in refresh()</p>. 182 */ bindFrictionImage(ImageView frictionImageView)183 private void bindFrictionImage(ImageView frictionImageView) { 184 if (frictionImageView == null || mFrictionSld == null) { 185 return; 186 } 187 if ((mAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE) 188 && (mAccessPoint.getSecurity() != AccessPoint.SECURITY_OWE)) { 189 mFrictionSld.setState(STATE_SECURED); 190 } else if (mAccessPoint.isMetered()) { 191 mFrictionSld.setState(STATE_METERED); 192 } 193 Drawable drawable = mFrictionSld.getCurrent(); 194 frictionImageView.setImageDrawable(drawable); 195 } 196 safeSetDefaultIcon()197 private void safeSetDefaultIcon() { 198 if (mDefaultIconResId != 0) { 199 setIcon(mDefaultIconResId); 200 } else { 201 setIcon(null); 202 } 203 } 204 updateBadge(Context context)205 protected void updateBadge(Context context) { 206 WifiConfiguration config = mAccessPoint.getConfig(); 207 if (config != null) { 208 // Fetch badge (may be null) 209 // Get the badge using a cache since the PM will ask the UserManager for the list 210 // of profiles every time otherwise. 211 } 212 } 213 214 /** 215 * Updates the title and summary; may indirectly call notifyChanged(). 216 */ refresh()217 public void refresh() { 218 setTitle(this, mAccessPoint); 219 final Context context = getContext(); 220 int level = mAccessPoint.getLevel(); 221 int wifiSpeed = Speed.NONE; 222 if (level != mLevel || wifiSpeed != mWifiSpeed) { 223 mLevel = level; 224 mWifiSpeed = wifiSpeed; 225 updateIcon(mLevel, context); 226 notifyChanged(); 227 } 228 229 updateBadge(context); 230 231 setSummary(mAccessPoint.getSettingsSummary()); 232 233 mContentDescription = buildContentDescription(getContext(), this /* pref */, mAccessPoint); 234 } 235 236 @Override notifyChanged()237 protected void notifyChanged() { 238 if (Looper.getMainLooper() != Looper.myLooper()) { 239 // Let our BG thread callbacks call setTitle/setSummary. 240 postNotifyChanged(); 241 } else { 242 super.notifyChanged(); 243 } 244 } 245 246 @VisibleForTesting setTitle(AccessPointPreference preference, AccessPoint ap)247 static void setTitle(AccessPointPreference preference, AccessPoint ap) { 248 preference.setTitle(ap.getTitle()); 249 } 250 251 /** 252 * Helper method to generate content description string. 253 */ 254 @VisibleForTesting buildContentDescription(Context context, Preference pref, AccessPoint ap)255 static CharSequence buildContentDescription(Context context, Preference pref, AccessPoint ap) { 256 CharSequence contentDescription = pref.getTitle(); 257 final CharSequence summary = pref.getSummary(); 258 if (!TextUtils.isEmpty(summary)) { 259 contentDescription = TextUtils.concat(contentDescription, ",", summary); 260 } 261 int level = ap.getLevel(); 262 if (level >= 0 && level < WIFI_CONNECTION_STRENGTH.length) { 263 contentDescription = TextUtils.concat(contentDescription, ",", 264 context.getString(WIFI_CONNECTION_STRENGTH[level])); 265 } 266 return TextUtils.concat(contentDescription, ",", 267 ap.getSecurity() == AccessPoint.SECURITY_NONE 268 ? context.getString(R.string.accessibility_wifi_security_type_none) 269 : context.getString(R.string.accessibility_wifi_security_type_secured)); 270 } 271 onLevelChanged()272 public void onLevelChanged() { 273 postNotifyChanged(); 274 } 275 postNotifyChanged()276 private void postNotifyChanged() { 277 if (mTitleView != null) { 278 mTitleView.post(mNotifyChanged); 279 } // Otherwise we haven't been bound yet, and don't need to update. 280 } 281 282 private final Runnable mNotifyChanged = new Runnable() { 283 @Override 284 public void run() { 285 notifyChanged(); 286 } 287 }; 288 289 public static class UserBadgeCache { 290 private final SparseArray<Drawable> mBadges = new SparseArray<>(); 291 private final PackageManager mPm; 292 UserBadgeCache(PackageManager pm)293 public UserBadgeCache(PackageManager pm) { 294 mPm = pm; 295 } 296 } 297 298 static class IconInjector { 299 private final Context mContext; 300 IconInjector(Context context)301 public IconInjector(Context context) { 302 mContext = context; 303 } 304 getIcon(int level)305 public Drawable getIcon(int level) { 306 return mContext.getDrawable(getWifiIconResource(level)); 307 } 308 } 309 310 static final int[] WIFI_PIE = { 311 R.drawable.ic_wifi_signal_0, 312 R.drawable.ic_wifi_signal_1, 313 R.drawable.ic_wifi_signal_2, 314 R.drawable.ic_wifi_signal_3, 315 R.drawable.ic_wifi_signal_4 316 }; 317 318 static final int[] SHOW_X_WIFI_PIE = { 319 R.drawable.ic_show_x_wifi_signal_0, 320 R.drawable.ic_show_x_wifi_signal_1, 321 R.drawable.ic_show_x_wifi_signal_2, 322 R.drawable.ic_show_x_wifi_signal_3, 323 R.drawable.ic_show_x_wifi_signal_4 324 }; 325 326 /** 327 * Returns the Wifi icon resource for a given RSSI level. 328 * 329 * @param level The number of bars to show (0-4) 330 * @throws IllegalArgumentException if an invalid RSSI level is given. 331 */ getWifiIconResource(int level)332 public static int getWifiIconResource(int level) { 333 return getWifiIconResource(false /* showX */, level); 334 } 335 336 /** 337 * Returns the Wifi icon resource for a given RSSI level. 338 * 339 * @param showX True if a connected Wi-Fi network has the problem which should show Pie+x 340 * signal icon to users. 341 * @param level The number of bars to show (0-4) 342 * @throws IllegalArgumentException if an invalid RSSI level is given. 343 */ getWifiIconResource(boolean showX, int level)344 public static int getWifiIconResource(boolean showX, int level) { 345 if (level < 0 || level >= WIFI_PIE.length) { 346 throw new IllegalArgumentException("No Wifi icon found for level: " + level); 347 } 348 return showX ? SHOW_X_WIFI_PIE[level] : WIFI_PIE[level]; 349 } 350 getColorAttr(Context context, int attr)351 public static ColorStateList getColorAttr(Context context, int attr) { 352 TypedArray ta = context.obtainStyledAttributes(new int[]{attr}); 353 ColorStateList stateList = null; 354 try { 355 stateList = ta.getColorStateList(0); 356 } finally { 357 ta.recycle(); 358 } 359 return stateList; 360 } 361 } 362