Home | History | Annotate | Download | only in wifi
      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