1 /*
2  * Copyright (C) 2019 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.res.Resources;
21 import android.content.res.TypedArray;
22 import android.graphics.drawable.Drawable;
23 import android.graphics.drawable.StateListDrawable;
24 import android.text.TextUtils;
25 import android.view.View;
26 import android.widget.ImageButton;
27 import android.widget.ImageView;
28 
29 import androidx.annotation.DrawableRes;
30 import androidx.annotation.NonNull;
31 import androidx.annotation.VisibleForTesting;
32 import androidx.preference.Preference;
33 import androidx.preference.PreferenceViewHolder;
34 
35 import com.android.settingslib.R;
36 import com.android.settingslib.Utils;
37 import com.android.wifitrackerlib.WifiEntry;
38 
39 /**
40  * Preference to display a WifiEntry in a wifi picker.
41  */
42 public class WifiEntryPreference extends Preference implements WifiEntry.WifiEntryCallback,
43         View.OnClickListener {
44 
45     private static final int[] STATE_SECURED = {
46             R.attr.state_encrypted
47     };
48 
49     private static final int[] FRICTION_ATTRS = {
50             R.attr.wifi_friction
51     };
52 
53     // These values must be kept within [WifiEntry.WIFI_LEVEL_MIN, WifiEntry.WIFI_LEVEL_MAX]
54     private static final int[] WIFI_CONNECTION_STRENGTH = {
55             R.string.accessibility_no_wifi,
56             R.string.accessibility_wifi_one_bar,
57             R.string.accessibility_wifi_two_bars,
58             R.string.accessibility_wifi_three_bars,
59             R.string.accessibility_wifi_signal_full
60     };
61 
62     // StateListDrawable to display secured lock / metered "$" icon
63     @Nullable private final StateListDrawable mFrictionSld;
64     private final IconInjector mIconInjector;
65     private WifiEntry mWifiEntry;
66     private int mLevel = -1;
67     private boolean mShowX; // Shows the Wi-Fi signl icon of Pie+x when it's true.
68     private CharSequence mContentDescription;
69     private OnButtonClickListener mOnButtonClickListener;
70 
WifiEntryPreference(@onNull Context context, @NonNull WifiEntry wifiEntry)71     public WifiEntryPreference(@NonNull Context context, @NonNull WifiEntry wifiEntry) {
72         this(context, wifiEntry, new IconInjector(context));
73     }
74 
75     @VisibleForTesting
WifiEntryPreference(@onNull Context context, @NonNull WifiEntry wifiEntry, @NonNull IconInjector iconInjector)76     WifiEntryPreference(@NonNull Context context, @NonNull WifiEntry wifiEntry,
77             @NonNull IconInjector iconInjector) {
78         super(context);
79 
80         setLayoutResource(R.layout.preference_access_point);
81         setWidgetLayoutResource(R.layout.access_point_friction_widget);
82         mFrictionSld = getFrictionStateListDrawable();
83         mWifiEntry = wifiEntry;
84         mWifiEntry.setListener(this);
85         mIconInjector = iconInjector;
86         refresh();
87     }
88 
getWifiEntry()89     public WifiEntry getWifiEntry() {
90         return mWifiEntry;
91     }
92 
93     @Override
onBindViewHolder(final PreferenceViewHolder view)94     public void onBindViewHolder(final PreferenceViewHolder view) {
95         super.onBindViewHolder(view);
96         final Drawable drawable = getIcon();
97         if (drawable != null) {
98             drawable.setLevel(mLevel);
99         }
100 
101         view.itemView.setContentDescription(mContentDescription);
102 
103         // Turn off divider
104         view.findViewById(R.id.two_target_divider).setVisibility(View.INVISIBLE);
105 
106         // Enable the icon button when the help string in this WifiEntry is not null.
107         final ImageButton imageButton = (ImageButton) view.findViewById(R.id.icon_button);
108         final ImageView frictionImageView = (ImageView) view.findViewById(
109                 R.id.friction_icon);
110         if (mWifiEntry.getHelpUriString() != null
111                 && mWifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_DISCONNECTED) {
112             final Drawable drawablehelp = getDrawable(R.drawable.ic_help);
113             drawablehelp.setTintList(
114                     Utils.getColorAttr(getContext(), android.R.attr.colorControlNormal));
115             ((ImageView) imageButton).setImageDrawable(drawablehelp);
116             imageButton.setVisibility(View.VISIBLE);
117             imageButton.setOnClickListener(this);
118             imageButton.setContentDescription(
119                     getContext().getText(R.string.help_label));
120 
121             if (frictionImageView != null) {
122                 frictionImageView.setVisibility(View.GONE);
123             }
124         } else {
125             imageButton.setVisibility(View.GONE);
126 
127             if (frictionImageView != null) {
128                 frictionImageView.setVisibility(View.VISIBLE);
129                 bindFrictionImage(frictionImageView);
130             }
131         }
132     }
133 
134     /**
135      * Updates the title and summary; may indirectly call notifyChanged().
136      */
refresh()137     public void refresh() {
138         setTitle(mWifiEntry.getTitle());
139         final int level = mWifiEntry.getLevel();
140         final boolean showX = mWifiEntry.shouldShowXLevelIcon();
141         if (level != mLevel || showX != mShowX) {
142             mLevel = level;
143             mShowX = showX;
144             updateIcon(mShowX, mLevel);
145             notifyChanged();
146         }
147 
148         setSummary(mWifiEntry.getSummary(false /* concise */));
149         mContentDescription = buildContentDescription();
150     }
151 
152     /**
153      * Indicates the state of the WifiEntry has changed and clients may retrieve updates through
154      * the WifiEntry getter methods.
155      */
onUpdated()156     public void onUpdated() {
157         // TODO(b/70983952): Fill this method in
158         refresh();
159     }
160 
161     /**
162      * Result of the connect request indicated by the WifiEntry.CONNECT_STATUS constants.
163      */
onConnectResult(int status)164     public void onConnectResult(int status) {
165         // TODO(b/70983952): Fill this method in
166     }
167 
168     /**
169      * Result of the disconnect request indicated by the WifiEntry.DISCONNECT_STATUS constants.
170      */
onDisconnectResult(int status)171     public void onDisconnectResult(int status) {
172         // TODO(b/70983952): Fill this method in
173     }
174 
175     /**
176      * Result of the forget request indicated by the WifiEntry.FORGET_STATUS constants.
177      */
onForgetResult(int status)178     public void onForgetResult(int status) {
179         // TODO(b/70983952): Fill this method in
180     }
181 
182     /**
183      * Result of the sign-in request indecated by the WifiEntry.SIGNIN_STATUS constants
184      */
onSignInResult(int status)185     public void onSignInResult(int status) {
186         // TODO(b/70983952): Fill this method in
187     }
188 
getIconColorAttr()189     protected int getIconColorAttr() {
190         final boolean accent = (mWifiEntry.hasInternetAccess()
191                 && mWifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED);
192         return accent ? android.R.attr.colorAccent : android.R.attr.colorControlNormal;
193     }
194 
updateIcon(boolean showX, int level)195     private void updateIcon(boolean showX, int level) {
196         if (level == -1) {
197             setIcon(null);
198             return;
199         }
200 
201         final Drawable drawable = mIconInjector.getIcon(showX, level);
202         if (drawable != null) {
203             drawable.setTint(Utils.getColorAttrDefaultColor(getContext(), getIconColorAttr()));
204             setIcon(drawable);
205         } else {
206             setIcon(null);
207         }
208     }
209 
210     @Nullable
getFrictionStateListDrawable()211     private StateListDrawable getFrictionStateListDrawable() {
212         TypedArray frictionSld;
213         try {
214             frictionSld = getContext().getTheme().obtainStyledAttributes(FRICTION_ATTRS);
215         } catch (Resources.NotFoundException e) {
216             // Fallback for platforms that do not need friction icon resources.
217             frictionSld = null;
218         }
219         return frictionSld != null ? (StateListDrawable) frictionSld.getDrawable(0) : null;
220     }
221 
222     /**
223      * Binds the friction icon drawable using a StateListDrawable.
224      *
225      * <p>Friction icons will be rebound when notifyChange() is called, and therefore
226      * do not need to be managed in refresh()</p>.
227      */
bindFrictionImage(ImageView frictionImageView)228     private void bindFrictionImage(ImageView frictionImageView) {
229         if (frictionImageView == null || mFrictionSld == null) {
230             return;
231         }
232         if ((mWifiEntry.getSecurity() != WifiEntry.SECURITY_NONE)
233                 && (mWifiEntry.getSecurity() != WifiEntry.SECURITY_OWE)) {
234             mFrictionSld.setState(STATE_SECURED);
235         }
236         frictionImageView.setImageDrawable(mFrictionSld.getCurrent());
237     }
238 
239     /**
240      * Helper method to generate content description string.
241      */
242     @VisibleForTesting
buildContentDescription()243     CharSequence buildContentDescription() {
244         final Context context = getContext();
245 
246         CharSequence contentDescription = getTitle();
247         final CharSequence summary = getSummary();
248         if (!TextUtils.isEmpty(summary)) {
249             contentDescription = TextUtils.concat(contentDescription, ",", summary);
250         }
251         int level = mWifiEntry.getLevel();
252         if (level >= 0 && level < WIFI_CONNECTION_STRENGTH.length) {
253             contentDescription = TextUtils.concat(contentDescription, ",",
254                     context.getString(WIFI_CONNECTION_STRENGTH[level]));
255         }
256         return TextUtils.concat(contentDescription, ",",
257                 mWifiEntry.getSecurity() == WifiEntry.SECURITY_NONE
258                         ? context.getString(R.string.accessibility_wifi_security_type_none)
259                         : context.getString(R.string.accessibility_wifi_security_type_secured));
260     }
261 
262 
263     static class IconInjector {
264         private final Context mContext;
265 
IconInjector(Context context)266         IconInjector(Context context) {
267             mContext = context;
268         }
269 
getIcon(boolean showX, int level)270         public Drawable getIcon(boolean showX, int level) {
271             return mContext.getDrawable(WifiUtils.getInternetIconResource(level, showX));
272         }
273     }
274 
275     /**
276      * Set listeners, who want to listen the button client event.
277      */
setOnButtonClickListener(OnButtonClickListener listener)278     public void setOnButtonClickListener(OnButtonClickListener listener) {
279         mOnButtonClickListener = listener;
280         notifyChanged();
281     }
282 
283     @Override
onClick(View view)284     public void onClick(View view) {
285         if (view.getId() == R.id.icon_button) {
286             if (mOnButtonClickListener != null) {
287                 mOnButtonClickListener.onButtonClick(this);
288             }
289         }
290     }
291 
292     /**
293      * Callback to inform the caller that the icon button is clicked.
294      */
295     public interface OnButtonClickListener {
296 
297         /**
298          * Register to listen the button click event.
299          */
onButtonClick(WifiEntryPreference preference)300         void onButtonClick(WifiEntryPreference preference);
301     }
302 
getDrawable(@rawableRes int iconResId)303     private Drawable getDrawable(@DrawableRes int iconResId) {
304         Drawable buttonIcon = null;
305 
306         try {
307             buttonIcon = getContext().getDrawable(iconResId);
308         } catch (Resources.NotFoundException exception) {
309             // Do nothing
310         }
311         return buttonIcon;
312     }
313 
314 }
315