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 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. 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 102 public AccessPointPreference(AccessPoint accessPoint, Context context, UserBadgeCache cache, 103 boolean forSavedNetworks) { 104 this(accessPoint, context, cache, 0 /* iconResId */, forSavedNetworks); 105 refresh(); 106 } 107 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 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 133 protected int getWidgetLayoutResourceId() { 134 return R.layout.access_point_friction_widget; 135 } 136 137 public AccessPoint getAccessPoint() { 138 return mAccessPoint; 139 } 140 141 @Override 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 168 public boolean shouldShowDivider() { 169 return mShowDivider; 170 } 171 172 public void setShowDivider(boolean showDivider) { 173 mShowDivider = showDivider; 174 notifyChanged(); 175 } 176 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 */ 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 212 private void safeSetDefaultIcon() { 213 if (mDefaultIconResId != 0) { 214 setIcon(mDefaultIconResId); 215 } else { 216 setIcon(null); 217 } 218 } 219 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 */ 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 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 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 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 293 public void onLevelChanged() { 294 postNotifyChanged(); 295 } 296 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 314 public UserBadgeCache(PackageManager pm) { 315 mPm = pm; 316 } 317 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 332 public IconInjector(Context context) { 333 mContext = context; 334 } 335 336 public Drawable getIcon(int level) { 337 return mContext.getDrawable(Utils.getWifiIconResource(level)); 338 } 339 } 340 } 341