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