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 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.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 */ 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 214 private void safeSetDefaultIcon() { 215 if (mDefaultIconResId != 0) { 216 setIcon(mDefaultIconResId); 217 } else { 218 setIcon(null); 219 } 220 } 221 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 */ 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 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 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 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 291 public void onLevelChanged() { 292 postNotifyChanged(); 293 } 294 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 312 public UserBadgeCache(PackageManager pm) { 313 mPm = pm; 314 } 315 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 330 public IconInjector(Context context) { 331 mContext = context; 332 } 333 334 public Drawable getIcon(int level) { 335 return mContext.getDrawable(Utils.getWifiIconResource(level)); 336 } 337 } 338 } 339