• 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.settingslib.wifi.AccessPoint;
25 import com.android.settingslib.wifi.AccessPoint.Speed;
26 import com.android.tv.settings.R;
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     mAccessPoint.setTag(this);
109     mLevel = level;
110     mDefaultIconResId = iconResId;
111     mFrictionSld = frictionSld;
112     mIconInjector = iconInjector;
113     mBadgePadding = context.getResources()
114         .getDimensionPixelSize(R.dimen.wifi_preference_badge_padding);
115   }
116 
getWidgetLayoutResourceId()117   protected int getWidgetLayoutResourceId() {
118     return R.layout.access_point_friction_widget;
119   }
120 
getAccessPoint()121   public AccessPoint getAccessPoint() {
122     return mAccessPoint;
123   }
124 
125   @Override
onBindViewHolder(final PreferenceViewHolder view)126   public void onBindViewHolder(final PreferenceViewHolder view) {
127     super.onBindViewHolder(view);
128     if (mAccessPoint == null) {
129       // Used for fake pref.
130       return;
131     }
132     Drawable drawable = getIcon();
133     if (drawable != null) {
134       drawable.setLevel(mLevel);
135     }
136 
137     mTitleView = (TextView) view.findViewById(android.R.id.title);
138     if (mTitleView != null) {
139       // Attach to the end of the title view
140       mTitleView.setCompoundDrawablesRelativeWithIntrinsicBounds(null, null, null, null);
141       mTitleView.setCompoundDrawablePadding(mBadgePadding);
142     }
143     view.itemView.setContentDescription(mContentDescription);
144 
145     ImageView frictionImageView = (ImageView) view.findViewById(R.id.friction_icon);
146     bindFrictionImage(frictionImageView);
147 
148     final View divider = view.findViewById(R.id.two_target_divider);
149     divider.setVisibility(shouldShowDivider() ? View.VISIBLE : View.INVISIBLE);
150   }
151 
shouldShowDivider()152   public boolean shouldShowDivider() {
153     return mShowDivider;
154   }
155 
setShowDivider(boolean showDivider)156   public void setShowDivider(boolean showDivider) {
157     mShowDivider = showDivider;
158     notifyChanged();
159   }
160 
updateIcon(int level, Context context)161   protected void updateIcon(int level, Context context) {
162     if (level == -1) {
163       safeSetDefaultIcon();
164       return;
165     }
166 
167     Drawable drawable = mIconInjector.getIcon(level);
168     if (!mForSavedNetworks && drawable != null) {
169       drawable.setTintList(getColorAttr(context, android.R.attr.colorControlNormal));
170       setIcon(drawable);
171     } else {
172       safeSetDefaultIcon();
173     }
174   }
175 
176   /**
177    * Binds the friction icon drawable using a StateListDrawable.
178    *
179    * <p>Friction icons will be rebound when notifyChange() is called, and therefore
180    * do not need to be managed in refresh()</p>.
181    */
bindFrictionImage(ImageView frictionImageView)182   private void bindFrictionImage(ImageView frictionImageView) {
183     if (frictionImageView == null || mFrictionSld == null) {
184       return;
185     }
186     if ((mAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE)
187         && (mAccessPoint.getSecurity() != AccessPoint.SECURITY_OWE)) {
188       mFrictionSld.setState(STATE_SECURED);
189     } else if (mAccessPoint.isMetered()) {
190       mFrictionSld.setState(STATE_METERED);
191     }
192     Drawable drawable = mFrictionSld.getCurrent();
193     frictionImageView.setImageDrawable(drawable);
194   }
195 
safeSetDefaultIcon()196   private void safeSetDefaultIcon() {
197     if (mDefaultIconResId != 0) {
198       setIcon(mDefaultIconResId);
199     } else {
200       setIcon(null);
201     }
202   }
203 
updateBadge(Context context)204   protected void updateBadge(Context context) {
205     WifiConfiguration config = mAccessPoint.getConfig();
206     if (config != null) {
207       // Fetch badge (may be null)
208       // Get the badge using a cache since the PM will ask the UserManager for the list
209       // of profiles every time otherwise.
210     }
211   }
212 
213   /**
214    * Updates the title and summary; may indirectly call notifyChanged().
215    */
refresh()216   public void refresh() {
217     setTitle(this, mAccessPoint);
218     final Context context = getContext();
219     int level = mAccessPoint.getLevel();
220     int wifiSpeed = Speed.NONE;
221     if (level != mLevel || wifiSpeed != mWifiSpeed) {
222       mLevel = level;
223       mWifiSpeed = wifiSpeed;
224       updateIcon(mLevel, context);
225       notifyChanged();
226     }
227 
228     updateBadge(context);
229 
230     setSummary(mAccessPoint.getSettingsSummary());
231 
232     mContentDescription = buildContentDescription(getContext(), this /* pref */, mAccessPoint);
233   }
234 
235   @Override
notifyChanged()236   protected void notifyChanged() {
237     if (Looper.getMainLooper() != Looper.myLooper()) {
238       // Let our BG thread callbacks call setTitle/setSummary.
239       postNotifyChanged();
240     } else {
241       super.notifyChanged();
242     }
243   }
244 
245   @VisibleForTesting
setTitle(AccessPointPreference preference, AccessPoint ap)246   static void setTitle(AccessPointPreference preference, AccessPoint ap) {
247     preference.setTitle(ap.getTitle());
248   }
249 
250   /**
251    * Helper method to generate content description string.
252    */
253   @VisibleForTesting
buildContentDescription(Context context, Preference pref, AccessPoint ap)254   static CharSequence buildContentDescription(Context context, Preference pref, AccessPoint ap) {
255     CharSequence contentDescription = pref.getTitle();
256     final CharSequence summary = pref.getSummary();
257     if (!TextUtils.isEmpty(summary)) {
258       contentDescription = TextUtils.concat(contentDescription, ",", summary);
259     }
260     int level = ap.getLevel();
261     if (level >= 0 && level < WIFI_CONNECTION_STRENGTH.length) {
262       contentDescription = TextUtils.concat(contentDescription, ",",
263           context.getString(WIFI_CONNECTION_STRENGTH[level]));
264     }
265     return TextUtils.concat(contentDescription, ",",
266         ap.getSecurity() == AccessPoint.SECURITY_NONE
267             ? context.getString(R.string.accessibility_wifi_security_type_none)
268             : context.getString(R.string.accessibility_wifi_security_type_secured));
269   }
270 
onLevelChanged()271   public void onLevelChanged() {
272     postNotifyChanged();
273   }
274 
postNotifyChanged()275   private void postNotifyChanged() {
276     if (mTitleView != null) {
277       mTitleView.post(mNotifyChanged);
278     } // Otherwise we haven't been bound yet, and don't need to update.
279   }
280 
281   private final Runnable mNotifyChanged = new Runnable() {
282     @Override
283     public void run() {
284       notifyChanged();
285     }
286   };
287 
288   public static class UserBadgeCache {
289     private final SparseArray<Drawable> mBadges = new SparseArray<>();
290     private final PackageManager mPm;
291 
UserBadgeCache(PackageManager pm)292     public UserBadgeCache(PackageManager pm) {
293       mPm = pm;
294     }
295   }
296 
297   static class IconInjector {
298     private final Context mContext;
299 
IconInjector(Context context)300     public IconInjector(Context context) {
301       mContext = context;
302     }
303 
getIcon(int level)304     public Drawable getIcon(int level) {
305       return mContext.getDrawable(getWifiIconResource(level));
306     }
307   }
308 
309   static final int[] WIFI_PIE = {
310       R.drawable.ic_wifi_signal_0,
311       R.drawable.ic_wifi_signal_1,
312       R.drawable.ic_wifi_signal_2,
313       R.drawable.ic_wifi_signal_3,
314       R.drawable.ic_wifi_signal_4
315   };
316 
317   static final int[] SHOW_X_WIFI_PIE = {
318       R.drawable.ic_show_x_wifi_signal_0,
319       R.drawable.ic_show_x_wifi_signal_1,
320       R.drawable.ic_show_x_wifi_signal_2,
321       R.drawable.ic_show_x_wifi_signal_3,
322       R.drawable.ic_show_x_wifi_signal_4
323   };
324 
325   /**
326    * Returns the Wifi icon resource for a given RSSI level.
327    *
328    * @param level The number of bars to show (0-4)
329    * @throws IllegalArgumentException if an invalid RSSI level is given.
330    */
getWifiIconResource(int level)331   public static int getWifiIconResource(int level) {
332     return getWifiIconResource(false /* showX */, level);
333   }
334 
335   /**
336    * Returns the Wifi icon resource for a given RSSI level.
337    *
338    * @param showX True if a connected Wi-Fi network has the problem which should show Pie+x
339    *              signal icon to users.
340    * @param level The number of bars to show (0-4)
341    * @throws IllegalArgumentException if an invalid RSSI level is given.
342    */
getWifiIconResource(boolean showX, int level)343   public static int getWifiIconResource(boolean showX, int level) {
344     if (level < 0 || level >= WIFI_PIE.length) {
345       throw new IllegalArgumentException("No Wifi icon found for level: " + level);
346     }
347     return showX ? SHOW_X_WIFI_PIE[level] : WIFI_PIE[level];
348   }
349 
getColorAttr(Context context, int attr)350   public static ColorStateList getColorAttr(Context context, int attr) {
351     TypedArray ta = context.obtainStyledAttributes(new int[]{attr});
352     ColorStateList stateList = null;
353     try {
354       stateList = ta.getColorStateList(0);
355     } finally {
356       ta.recycle();
357     }
358     return stateList;
359   }
360 }
361