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