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.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