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