OSDN Git Service

a687b935393edb6d43b2c6ceca94ea8267009340
[android-x86/packages-apps-Settings.git] / src / com / android / settings / wifi / slice / WifiSlice.java
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
78     public WifiSlice(Context context) {
79         mContext = context;
80         mWifiManager = mContext.getSystemService(WifiManager.class);
81         mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
82     }
83
84     @Override
85     public Uri getUri() {
86         return WIFI_SLICE_URI;
87     }
88
89     @Override
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             return listBuilder.build();
98         }
99
100         final WifiScanWorker worker = SliceBackgroundWorker.getInstance(getUri());
101         final List<AccessPoint> apList = worker != null ? worker.getResults() : null;
102         final int apCount = apList == null ? 0 : apList.size();
103         final boolean isFirstApActive = apCount > 0 && apList.get(0).isActive();
104         handleNetworkCallback(worker, isFirstApActive);
105
106         // Need a loading text when results are not ready or out of date.
107         boolean needLoadingRow = true;
108         // Skip checking the existence of the first access point if it's active
109         int index = isFirstApActive ? 1 : 0;
110         // This loop checks the existence of reachable APs to determine the validity of the current
111         // AP list.
112         for (; index < apCount; index++) {
113             if (apList.get(index).isReachable()) {
114                 needLoadingRow = false;
115                 break;
116             }
117         }
118
119         // Add AP rows
120         final CharSequence placeholder = mContext.getText(R.string.summary_placeholder);
121         for (int i = 0; i < DEFAULT_EXPANDED_ROW_COUNT; i++) {
122             if (i < apCount) {
123                 listBuilder.addRow(getAccessPointRow(apList.get(i)));
124             } else if (needLoadingRow) {
125                 listBuilder.addRow(getLoadingRow(placeholder));
126                 needLoadingRow = false;
127             } else {
128                 listBuilder.addRow(new ListBuilder.RowBuilder()
129                         .setTitle(placeholder)
130                         .setSubtitle(placeholder));
131             }
132         }
133         return listBuilder.build();
134     }
135
136     private ListBuilder getHeaderRow(boolean isWifiEnabled) {
137         final IconCompat icon = IconCompat.createWithResource(mContext,
138                 R.drawable.ic_settings_wireless);
139         final String title = mContext.getString(R.string.wifi_settings);
140         final CharSequence summary = getSummary();
141         final PendingIntent toggleAction = getBroadcastIntent(mContext);
142         final PendingIntent primaryAction = getPrimaryAction();
143         final SliceAction primarySliceAction = SliceAction.createDeeplink(primaryAction, icon,
144                 ListBuilder.ICON_IMAGE, title);
145         final SliceAction toggleSliceAction = SliceAction.createToggle(toggleAction,
146                 null /* actionTitle */, isWifiEnabled);
147
148         return new ListBuilder(mContext, getUri(), ListBuilder.INFINITY)
149                 .setAccentColor(COLOR_NOT_TINTED)
150                 .setKeywords(getKeywords())
151                 .addRow(new ListBuilder.RowBuilder()
152                         .setTitle(title)
153                         .setSubtitle(summary)
154                         .addEndItem(toggleSliceAction)
155                         .setPrimaryAction(primarySliceAction));
156     }
157
158     private void handleNetworkCallback(WifiScanWorker worker, boolean isFirstApActive) {
159         if (worker == null) {
160             return;
161         }
162         if (isFirstApActive) {
163             worker.registerNetworkCallback(mWifiManager.getCurrentNetwork());
164         } else {
165             worker.unregisterNetworkCallback();
166         }
167     }
168
169     private ListBuilder.RowBuilder getAccessPointRow(AccessPoint accessPoint) {
170         final boolean isCaptivePortal = accessPoint.isActive() && isCaptivePortal();
171         final CharSequence title = accessPoint.getTitle();
172         final CharSequence summary = getAccessPointSummary(accessPoint, isCaptivePortal);
173         final IconCompat levelIcon = getAccessPointLevelIcon(accessPoint);
174         final ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder()
175                 .setTitleItem(levelIcon, ListBuilder.ICON_IMAGE)
176                 .setTitle(title)
177                 .setSubtitle(summary)
178                 .setPrimaryAction(SliceAction.createDeeplink(
179                         getAccessPointAction(accessPoint, isCaptivePortal), levelIcon,
180                         ListBuilder.ICON_IMAGE, title));
181
182         if (isCaptivePortal) {
183             rowBuilder.addEndItem(getCaptivePortalEndAction(accessPoint, title));
184         } else {
185             final IconCompat endIcon = getEndIcon(accessPoint);
186             if (endIcon != null) {
187                 rowBuilder.addEndItem(endIcon, ListBuilder.ICON_IMAGE);
188             }
189         }
190         return rowBuilder;
191     }
192
193     private CharSequence getAccessPointSummary(AccessPoint accessPoint, boolean isCaptivePortal) {
194         if (isCaptivePortal) {
195             return mContext.getText(R.string.wifi_tap_to_sign_in);
196         }
197
198         final CharSequence summary = accessPoint.getSettingsSummary();
199         return TextUtils.isEmpty(summary) ? mContext.getText(R.string.disconnected) : summary;
200     }
201
202     private IconCompat getAccessPointLevelIcon(AccessPoint accessPoint) {
203         final Drawable d = mContext.getDrawable(
204                 com.android.settingslib.Utils.getWifiIconResource(accessPoint.getLevel()));
205
206         @ColorInt int color;
207         if (accessPoint.isActive()) {
208             final NetworkInfo.State state = accessPoint.getNetworkInfo().getState();
209             if (state == NetworkInfo.State.CONNECTED) {
210                 color = Utils.getColorAccentDefaultColor(mContext);
211             } else { // connecting
212                 color = Utils.getDisabled(mContext, Utils.getColorAttrDefaultColor(mContext,
213                         android.R.attr.colorControlNormal));
214             }
215         } else {
216             color = Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorControlNormal);
217         }
218
219         d.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN));
220         return Utils.createIconWithDrawable(d);
221     }
222
223     private IconCompat getEndIcon(AccessPoint accessPoint) {
224         if (accessPoint.isActive()) {
225             return null;
226         } else if (accessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
227             return IconCompat.createWithResource(mContext, R.drawable.ic_friction_lock_closed);
228         } else if (accessPoint.isMetered()) {
229             return IconCompat.createWithResource(mContext, R.drawable.ic_friction_money);
230         }
231         return null;
232     }
233
234     private SliceAction getCaptivePortalEndAction(AccessPoint accessPoint, CharSequence title) {
235         return SliceAction.createDeeplink(
236                 getAccessPointAction(accessPoint, false /* isCaptivePortal */),
237                 IconCompat.createWithResource(mContext, R.drawable.ic_settings_accent),
238                 ListBuilder.ICON_IMAGE, title);
239     }
240
241     private PendingIntent getAccessPointAction(AccessPoint accessPoint, boolean isCaptivePortal) {
242         final Bundle extras = new Bundle();
243         accessPoint.saveWifiState(extras);
244
245         Intent intent;
246         if (isCaptivePortal) {
247             intent = new Intent(mContext, ConnectToWifiHandler.class);
248             intent.putExtra(ConnectivityManager.EXTRA_NETWORK, mWifiManager.getCurrentNetwork());
249         } else if (accessPoint.isActive()) {
250             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         } else if (WifiUtils.getConnectingType(accessPoint) != WifiUtils.CONNECT_TYPE_OTHERS) {
257             intent = new Intent(mContext, ConnectToWifiHandler.class);
258             intent.putExtra(WifiDialogActivity.KEY_ACCESS_POINT_STATE, extras);
259         } else {
260             intent = new Intent(mContext, WifiDialogActivity.class);
261             intent.putExtra(WifiDialogActivity.KEY_ACCESS_POINT_STATE, extras);
262         }
263         return PendingIntent.getActivity(mContext, accessPoint.hashCode() /* requestCode */,
264                 intent, 0 /* flags */);
265     }
266
267     private ListBuilder.RowBuilder getLoadingRow(CharSequence placeholder) {
268         final CharSequence title = mContext.getText(R.string.wifi_empty_list_wifi_on);
269
270         // for aligning to the Wi-Fi AP's name
271         final IconCompat emptyIcon = Utils.createIconWithDrawable(
272                 new ColorDrawable(Color.TRANSPARENT));
273
274         return new ListBuilder.RowBuilder()
275                 .setTitleItem(emptyIcon, ListBuilder.ICON_IMAGE)
276                 .setTitle(placeholder)
277                 .setSubtitle(title);
278     }
279
280     protected boolean isCaptivePortal() {
281         final NetworkCapabilities nc = mConnectivityManager.getNetworkCapabilities(
282                 mWifiManager.getCurrentNetwork());
283         return WifiUtils.canSignIntoNetwork(nc);
284     }
285
286     /**
287      * Update the current wifi status to the boolean value keyed by
288      * {@link android.app.slice.Slice#EXTRA_TOGGLE_STATE} on {@param intent}.
289      */
290     @Override
291     public void onNotifyChange(Intent intent) {
292         final boolean newState = intent.getBooleanExtra(EXTRA_TOGGLE_STATE,
293                 mWifiManager.isWifiEnabled());
294         mWifiManager.setWifiEnabled(newState);
295         // Do not notifyChange on Uri. The service takes longer to update the current value than it
296         // does for the Slice to check the current value again. Let {@link WifiScanWorker}
297         // handle it.
298     }
299
300     @Override
301     public Intent getIntent() {
302         final String screenTitle = mContext.getText(R.string.wifi_settings).toString();
303         final Uri contentUri = new Uri.Builder().appendPath(KEY_WIFI).build();
304         final Intent intent = SliceBuilderUtils.buildSearchResultPageIntent(mContext,
305                 WifiSettings.class.getName(), KEY_WIFI, screenTitle,
306                 SettingsEnums.DIALOG_WIFI_AP_EDIT)
307                 .setClassName(mContext.getPackageName(), SubSettings.class.getName())
308                 .setData(contentUri);
309
310         return intent;
311     }
312
313     private boolean isWifiEnabled() {
314         switch (mWifiManager.getWifiState()) {
315             case WifiManager.WIFI_STATE_ENABLED:
316             case WifiManager.WIFI_STATE_ENABLING:
317                 return true;
318             default:
319                 return false;
320         }
321     }
322
323     private CharSequence getSummary() {
324         switch (mWifiManager.getWifiState()) {
325             case WifiManager.WIFI_STATE_ENABLED:
326             case WifiManager.WIFI_STATE_ENABLING:
327                 return mContext.getText(R.string.switch_on_text);
328             case WifiManager.WIFI_STATE_DISABLED:
329             case WifiManager.WIFI_STATE_DISABLING:
330                 return mContext.getText(R.string.switch_off_text);
331             case WifiManager.WIFI_STATE_UNKNOWN:
332             default:
333                 return null;
334         }
335     }
336
337     private PendingIntent getPrimaryAction() {
338         final Intent intent = getIntent();
339         return PendingIntent.getActivity(mContext, 0 /* requestCode */,
340                 intent, 0 /* flags */);
341     }
342
343     private Set<String> getKeywords() {
344         final String keywords = mContext.getString(R.string.keywords_wifi);
345         return Arrays.asList(TextUtils.split(keywords, ","))
346                 .stream()
347                 .map(String::trim)
348                 .collect(Collectors.toSet());
349     }
350
351     @Override
352     public Class getBackgroundWorkerClass() {
353         return WifiScanWorker.class;
354     }
355 }