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.text.TextUtils; 29 import android.util.AttributeSet; 30 import android.util.SparseArray; 31 import android.view.View; 32 import android.widget.ImageView; 33 import android.widget.TextView; 34 35 import androidx.annotation.VisibleForTesting; 36 import androidx.preference.Preference; 37 import androidx.preference.PreferenceViewHolder; 38 39 import com.android.settingslib.R; 40 import com.android.settingslib.TronUtils; 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 fake 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 fake 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.setTintList(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 && (mAccessPoint.getSecurity() != AccessPoint.SECURITY_OWE)) { 205 mFrictionSld.setState(STATE_SECURED); 206 } else if (mAccessPoint.isMetered()) { 207 mFrictionSld.setState(STATE_METERED); 208 } 209 Drawable drawable = mFrictionSld.getCurrent(); 210 frictionImageView.setImageDrawable(drawable); 211 } 212 safeSetDefaultIcon()213 private void safeSetDefaultIcon() { 214 if (mDefaultIconResId != 0) { 215 setIcon(mDefaultIconResId); 216 } else { 217 setIcon(null); 218 } 219 } 220 updateBadge(Context context)221 protected void updateBadge(Context context) { 222 WifiConfiguration config = mAccessPoint.getConfig(); 223 if (config != null) { 224 // Fetch badge (may be null) 225 // Get the badge using a cache since the PM will ask the UserManager for the list 226 // of profiles every time otherwise. 227 mBadge = mBadgeCache.getUserBadge(config.creatorUid); 228 } 229 } 230 231 /** 232 * Updates the title and summary; may indirectly call notifyChanged(). 233 */ refresh()234 public void refresh() { 235 setTitle(this, mAccessPoint); 236 final Context context = getContext(); 237 int level = mAccessPoint.getLevel(); 238 int wifiSpeed = mAccessPoint.getSpeed(); 239 if (level != mLevel || wifiSpeed != mWifiSpeed) { 240 mLevel = level; 241 mWifiSpeed = wifiSpeed; 242 updateIcon(mLevel, context); 243 notifyChanged(); 244 } 245 246 updateBadge(context); 247 248 setSummary(mForSavedNetworks ? mAccessPoint.getSavedNetworkSummary() 249 : mAccessPoint.getSettingsSummary()); 250 251 mContentDescription = buildContentDescription(getContext(), this /* pref */, mAccessPoint); 252 } 253 254 @Override notifyChanged()255 protected void notifyChanged() { 256 if (Looper.getMainLooper() != Looper.myLooper()) { 257 // Let our BG thread callbacks call setTitle/setSummary. 258 postNotifyChanged(); 259 } else { 260 super.notifyChanged(); 261 } 262 } 263 264 @VisibleForTesting setTitle(AccessPointPreference preference, AccessPoint ap)265 static void setTitle(AccessPointPreference preference, AccessPoint ap) { 266 preference.setTitle(ap.getTitle()); 267 } 268 269 /** 270 * Helper method to generate content description string. 271 */ 272 @VisibleForTesting buildContentDescription(Context context, Preference pref, AccessPoint ap)273 static CharSequence buildContentDescription(Context context, Preference pref, AccessPoint ap) { 274 CharSequence contentDescription = pref.getTitle(); 275 final CharSequence summary = pref.getSummary(); 276 if (!TextUtils.isEmpty(summary)) { 277 contentDescription = TextUtils.concat(contentDescription, ",", summary); 278 } 279 int level = ap.getLevel(); 280 if (level >= 0 && level < WIFI_CONNECTION_STRENGTH.length) { 281 contentDescription = TextUtils.concat(contentDescription, ",", 282 context.getString(WIFI_CONNECTION_STRENGTH[level])); 283 } 284 return TextUtils.concat(contentDescription, ",", 285 ap.getSecurity() == AccessPoint.SECURITY_NONE 286 ? context.getString(R.string.accessibility_wifi_security_type_none) 287 : context.getString(R.string.accessibility_wifi_security_type_secured)); 288 } 289 onLevelChanged()290 public void onLevelChanged() { 291 postNotifyChanged(); 292 } 293 postNotifyChanged()294 private void postNotifyChanged() { 295 if (mTitleView != null) { 296 mTitleView.post(mNotifyChanged); 297 } // Otherwise we haven't been bound yet, and don't need to update. 298 } 299 300 private final Runnable mNotifyChanged = new Runnable() { 301 @Override 302 public void run() { 303 notifyChanged(); 304 } 305 }; 306 307 public static class UserBadgeCache { 308 private final SparseArray<Drawable> mBadges = new SparseArray<>(); 309 private final PackageManager mPm; 310 UserBadgeCache(PackageManager pm)311 public UserBadgeCache(PackageManager pm) { 312 mPm = pm; 313 } 314 getUserBadge(int userId)315 private Drawable getUserBadge(int userId) { 316 int index = mBadges.indexOfKey(userId); 317 if (index < 0) { 318 Drawable badge = mPm.getUserBadgeForDensity(new UserHandle(userId), 0 /* dpi */); 319 mBadges.put(userId, badge); 320 return badge; 321 } 322 return mBadges.valueAt(index); 323 } 324 } 325 326 static class IconInjector { 327 private final Context mContext; 328 IconInjector(Context context)329 public IconInjector(Context context) { 330 mContext = context; 331 } 332 getIcon(int level)333 public Drawable getIcon(int level) { 334 return mContext.getDrawable(Utils.getWifiIconResource(level)); 335 } 336 } 337 } 338