• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 
17 package com.android.settings.wifi.slice;
18 
19 import static android.app.slice.Slice.EXTRA_TOGGLE_STATE;
20 import static android.provider.SettingsSlicesContract.KEY_WIFI;
21 
22 import static com.android.settings.slices.CustomSliceRegistry.WIFI_SLICE_URI;
23 
24 import android.annotation.ColorInt;
25 import android.app.PendingIntent;
26 import android.app.settings.SettingsEnums;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.graphics.Color;
30 import android.graphics.PorterDuff;
31 import android.graphics.PorterDuffColorFilter;
32 import android.graphics.drawable.ColorDrawable;
33 import android.graphics.drawable.Drawable;
34 import android.net.ConnectivityManager;
35 import android.net.NetworkCapabilities;
36 import android.net.NetworkInfo;
37 import android.net.Uri;
38 import android.net.wifi.WifiManager;
39 import android.os.Bundle;
40 import android.text.TextUtils;
41 
42 import androidx.annotation.VisibleForTesting;
43 import androidx.core.graphics.drawable.IconCompat;
44 import androidx.slice.Slice;
45 import androidx.slice.builders.ListBuilder;
46 import androidx.slice.builders.SliceAction;
47 
48 import com.android.settings.R;
49 import com.android.settings.SubSettings;
50 import com.android.settings.Utils;
51 import com.android.settings.core.SubSettingLauncher;
52 import com.android.settings.slices.CustomSliceable;
53 import com.android.settings.slices.SliceBackgroundWorker;
54 import com.android.settings.slices.SliceBuilderUtils;
55 import com.android.settings.wifi.WifiDialogActivity;
56 import com.android.settings.wifi.WifiSettings;
57 import com.android.settings.wifi.WifiUtils;
58 import com.android.settings.wifi.details.WifiNetworkDetailsFragment;
59 import com.android.settingslib.wifi.AccessPoint;
60 
61 import java.util.Arrays;
62 import java.util.List;
63 import java.util.Set;
64 import java.util.stream.Collectors;
65 
66 /**
67  * {@link CustomSliceable} for Wi-Fi, used by generic clients.
68  */
69 public class WifiSlice implements CustomSliceable {
70 
71     @VisibleForTesting
72     static final int DEFAULT_EXPANDED_ROW_COUNT = 3;
73 
74     protected final Context mContext;
75     protected final WifiManager mWifiManager;
76     protected final ConnectivityManager mConnectivityManager;
77 
WifiSlice(Context context)78     public WifiSlice(Context context) {
79         mContext = context;
80         mWifiManager = mContext.getSystemService(WifiManager.class);
81         mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
82     }
83 
84     @Override
getUri()85     public Uri getUri() {
86         return WIFI_SLICE_URI;
87     }
88 
89     @Override
getSlice()90     public Slice getSlice() {
91         // Reload theme for switching dark mode on/off
92         mContext.getTheme().applyStyle(R.style.Theme_Settings_Home, true /* force */);
93 
94         final boolean isWifiEnabled = isWifiEnabled();
95         ListBuilder listBuilder = getHeaderRow(isWifiEnabled);
96         if (!isWifiEnabled) {
97             WifiScanWorker.clearClickedWifi();
98             return listBuilder.build();
99         }
100 
101         final WifiScanWorker worker = SliceBackgroundWorker.getInstance(getUri());
102         final List<AccessPoint> apList = worker != null ? worker.getResults() : null;
103         final int apCount = apList == null ? 0 : apList.size();
104         final boolean isFirstApActive = apCount > 0 && apList.get(0).isActive();
105         handleNetworkCallback(worker, isFirstApActive);
106 
107         // Need a loading text when results are not ready or out of date.
108         boolean needLoadingRow = true;
109         // Skip checking the existence of the first access point if it's active
110         int index = isFirstApActive ? 1 : 0;
111         // This loop checks the existence of reachable APs to determine the validity of the current
112         // AP list.
113         for (; index < apCount; index++) {
114             if (apList.get(index).isReachable()) {
115                 needLoadingRow = false;
116                 break;
117             }
118         }
119 
120         // Add AP rows
121         final CharSequence placeholder = mContext.getText(R.string.summary_placeholder);
122         for (int i = 0; i < DEFAULT_EXPANDED_ROW_COUNT; i++) {
123             if (i < apCount) {
124                 listBuilder.addRow(getAccessPointRow(apList.get(i)));
125             } else if (needLoadingRow) {
126                 listBuilder.addRow(getLoadingRow(placeholder));
127                 needLoadingRow = false;
128             } else {
129                 listBuilder.addRow(new ListBuilder.RowBuilder()
130                         .setTitle(placeholder)
131                         .setSubtitle(placeholder));
132             }
133         }
134         return listBuilder.build();
135     }
136 
handleNetworkCallback(WifiScanWorker worker, boolean isFirstApActive)137     private void handleNetworkCallback(WifiScanWorker worker, boolean isFirstApActive) {
138         if (worker == null) {
139             return;
140         }
141         if (isFirstApActive) {
142             worker.registerNetworkCallback(mWifiManager.getCurrentNetwork());
143         } else {
144             worker.unregisterNetworkCallback();
145         }
146     }
147 
getHeaderRow(boolean isWifiEnabled)148     private ListBuilder getHeaderRow(boolean isWifiEnabled) {
149         final IconCompat icon = IconCompat.createWithResource(mContext,
150                 R.drawable.ic_settings_wireless);
151         final String title = mContext.getString(R.string.wifi_settings);
152         final PendingIntent toggleAction = getBroadcastIntent(mContext);
153         final PendingIntent primaryAction = getPrimaryAction();
154         final SliceAction primarySliceAction = SliceAction.createDeeplink(primaryAction, icon,
155                 ListBuilder.ICON_IMAGE, title);
156         final SliceAction toggleSliceAction = SliceAction.createToggle(toggleAction,
157                 null /* actionTitle */, isWifiEnabled);
158 
159         return new ListBuilder(mContext, getUri(), ListBuilder.INFINITY)
160                 .setAccentColor(COLOR_NOT_TINTED)
161                 .setKeywords(getKeywords())
162                 .addRow(new ListBuilder.RowBuilder()
163                         .setTitle(title)
164                         .addEndItem(toggleSliceAction)
165                         .setPrimaryAction(primarySliceAction));
166     }
167 
getAccessPointRow(AccessPoint accessPoint)168     private ListBuilder.RowBuilder getAccessPointRow(AccessPoint accessPoint) {
169         final boolean isCaptivePortal = accessPoint.isActive() && isCaptivePortal();
170         final CharSequence title = accessPoint.getTitle();
171         final CharSequence summary = getAccessPointSummary(accessPoint, isCaptivePortal);
172         final IconCompat levelIcon = getAccessPointLevelIcon(accessPoint);
173         final ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder()
174                 .setTitleItem(levelIcon, ListBuilder.ICON_IMAGE)
175                 .setTitle(title)
176                 .setSubtitle(summary)
177                 .setPrimaryAction(getAccessPointAction(accessPoint, isCaptivePortal, levelIcon,
178                         title));
179 
180         if (isCaptivePortal) {
181             rowBuilder.addEndItem(getCaptivePortalEndAction(accessPoint, title));
182         } else {
183             final IconCompat endIcon = getEndIcon(accessPoint);
184             if (endIcon != null) {
185                 rowBuilder.addEndItem(endIcon, ListBuilder.ICON_IMAGE);
186             }
187         }
188         return rowBuilder;
189     }
190 
getAccessPointSummary(AccessPoint accessPoint, boolean isCaptivePortal)191     private CharSequence getAccessPointSummary(AccessPoint accessPoint, boolean isCaptivePortal) {
192         if (isCaptivePortal) {
193             return mContext.getText(R.string.wifi_tap_to_sign_in);
194         }
195 
196         final CharSequence summary = accessPoint.getSettingsSummary();
197         return TextUtils.isEmpty(summary) ? mContext.getText(R.string.disconnected) : summary;
198     }
199 
getAccessPointLevelIcon(AccessPoint accessPoint)200     private IconCompat getAccessPointLevelIcon(AccessPoint accessPoint) {
201         final Drawable d = mContext.getDrawable(
202                 com.android.settingslib.Utils.getWifiIconResource(accessPoint.getLevel()));
203 
204         final @ColorInt int color;
205         if (accessPoint.isActive()) {
206             final NetworkInfo.State state = accessPoint.getNetworkInfo().getState();
207             if (state == NetworkInfo.State.CONNECTED) {
208                 color = Utils.getColorAccentDefaultColor(mContext);
209             } else { // connecting
210                 color = Utils.getDisabled(mContext, Utils.getColorAttrDefaultColor(mContext,
211                         android.R.attr.colorControlNormal));
212             }
213         } else {
214             color = Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorControlNormal);
215         }
216 
217         d.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
218         return Utils.createIconWithDrawable(d);
219     }
220 
getEndIcon(AccessPoint accessPoint)221     private IconCompat getEndIcon(AccessPoint accessPoint) {
222         if (accessPoint.isActive()) {
223             return null;
224         } else if (accessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
225             return IconCompat.createWithResource(mContext, R.drawable.ic_friction_lock_closed);
226         } else if (accessPoint.isMetered()) {
227             return IconCompat.createWithResource(mContext, R.drawable.ic_friction_money);
228         }
229         return null;
230     }
231 
getCaptivePortalEndAction(AccessPoint accessPoint, CharSequence title)232     private SliceAction getCaptivePortalEndAction(AccessPoint accessPoint, CharSequence title) {
233         return getAccessPointAction(accessPoint, false /* isCaptivePortal */,
234                 IconCompat.createWithResource(mContext, R.drawable.ic_settings_accent), title);
235     }
236 
getAccessPointAction(AccessPoint accessPoint, boolean isCaptivePortal, IconCompat icon, CharSequence title)237     private SliceAction getAccessPointAction(AccessPoint accessPoint, boolean isCaptivePortal,
238             IconCompat icon, CharSequence title) {
239         final int requestCode = accessPoint.hashCode();
240         if (isCaptivePortal) {
241             final Intent intent = new Intent(mContext, ConnectToWifiHandler.class)
242                     .putExtra(ConnectivityManager.EXTRA_NETWORK, mWifiManager.getCurrentNetwork());
243             return getBroadcastAction(requestCode, intent, icon, title);
244         }
245 
246         final Bundle extras = new Bundle();
247         accessPoint.saveWifiState(extras);
248 
249         if (accessPoint.isActive()) {
250             final Intent intent = new SubSettingLauncher(mContext)
251                     .setTitleRes(R.string.pref_title_network_details)
252                     .setDestination(WifiNetworkDetailsFragment.class.getName())
253                     .setArguments(extras)
254                     .setSourceMetricsCategory(SettingsEnums.WIFI)
255                     .toIntent();
256             return getActivityAction(requestCode, intent, icon, title);
257         } else if (WifiUtils.getConnectingType(accessPoint) != WifiUtils.CONNECT_TYPE_OTHERS) {
258             final Intent intent = new Intent(mContext, ConnectToWifiHandler.class)
259                     .putExtra(WifiDialogActivity.KEY_ACCESS_POINT_STATE, extras);
260             return getBroadcastAction(requestCode, intent, icon, title);
261         } else {
262             final Intent intent = new Intent(mContext, WifiDialogActivity.class)
263                     .putExtra(WifiDialogActivity.KEY_ACCESS_POINT_STATE, extras);
264             return getActivityAction(requestCode, intent, icon, title);
265         }
266     }
267 
getActivityAction(int requestCode, Intent intent, IconCompat icon, CharSequence title)268     private SliceAction getActivityAction(int requestCode, Intent intent, IconCompat icon,
269             CharSequence title) {
270         final PendingIntent pi = PendingIntent.getActivity(mContext, requestCode, intent,
271                 0 /* flags */);
272         return SliceAction.createDeeplink(pi, icon, ListBuilder.ICON_IMAGE, title);
273     }
274 
getBroadcastAction(int requestCode, Intent intent, IconCompat icon, CharSequence title)275     private SliceAction getBroadcastAction(int requestCode, Intent intent, IconCompat icon,
276             CharSequence title) {
277         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
278         final PendingIntent pi = PendingIntent.getBroadcast(mContext, requestCode, intent,
279                 PendingIntent.FLAG_UPDATE_CURRENT);
280         return SliceAction.create(pi, icon, ListBuilder.ICON_IMAGE, title);
281     }
282 
getLoadingRow(CharSequence placeholder)283     private ListBuilder.RowBuilder getLoadingRow(CharSequence placeholder) {
284         final CharSequence title = mContext.getText(R.string.wifi_empty_list_wifi_on);
285 
286         // for aligning to the Wi-Fi AP's name
287         final IconCompat emptyIcon = Utils.createIconWithDrawable(
288                 new ColorDrawable(Color.TRANSPARENT));
289 
290         return new ListBuilder.RowBuilder()
291                 .setTitleItem(emptyIcon, ListBuilder.ICON_IMAGE)
292                 .setTitle(placeholder)
293                 .setSubtitle(title);
294     }
295 
isCaptivePortal()296     private boolean isCaptivePortal() {
297         final NetworkCapabilities nc = mConnectivityManager.getNetworkCapabilities(
298                 mWifiManager.getCurrentNetwork());
299         return WifiUtils.canSignIntoNetwork(nc);
300     }
301 
302     /**
303      * Update the current wifi status to the boolean value keyed by
304      * {@link android.app.slice.Slice#EXTRA_TOGGLE_STATE} on {@param intent}.
305      */
306     @Override
onNotifyChange(Intent intent)307     public void onNotifyChange(Intent intent) {
308         final boolean newState = intent.getBooleanExtra(EXTRA_TOGGLE_STATE,
309                 mWifiManager.isWifiEnabled());
310         mWifiManager.setWifiEnabled(newState);
311         // Do not notifyChange on Uri. The service takes longer to update the current value than it
312         // does for the Slice to check the current value again. Let {@link WifiScanWorker}
313         // handle it.
314     }
315 
316     @Override
getIntent()317     public Intent getIntent() {
318         final String screenTitle = mContext.getText(R.string.wifi_settings).toString();
319         final Uri contentUri = new Uri.Builder().appendPath(KEY_WIFI).build();
320         final Intent intent = SliceBuilderUtils.buildSearchResultPageIntent(mContext,
321                 WifiSettings.class.getName(), KEY_WIFI, screenTitle,
322                 SettingsEnums.DIALOG_WIFI_AP_EDIT)
323                 .setClassName(mContext.getPackageName(), SubSettings.class.getName())
324                 .setData(contentUri);
325 
326         return intent;
327     }
328 
isWifiEnabled()329     private boolean isWifiEnabled() {
330         switch (mWifiManager.getWifiState()) {
331             case WifiManager.WIFI_STATE_ENABLED:
332             case WifiManager.WIFI_STATE_ENABLING:
333                 return true;
334             default:
335                 return false;
336         }
337     }
338 
getPrimaryAction()339     private PendingIntent getPrimaryAction() {
340         final Intent intent = getIntent();
341         return PendingIntent.getActivity(mContext, 0 /* requestCode */,
342                 intent, 0 /* flags */);
343     }
344 
getKeywords()345     private Set<String> getKeywords() {
346         final String keywords = mContext.getString(R.string.keywords_wifi);
347         return Arrays.asList(TextUtils.split(keywords, ","))
348                 .stream()
349                 .map(String::trim)
350                 .collect(Collectors.toSet());
351     }
352 
353     @Override
getBackgroundWorkerClass()354     public Class getBackgroundWorkerClass() {
355         return WifiScanWorker.class;
356     }
357 }
358