• 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 import static com.android.settingslib.wifi.WifiUtils.getHotspotIconResource;
24 
25 import android.annotation.ColorInt;
26 import android.app.PendingIntent;
27 import android.app.settings.SettingsEnums;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.pm.PackageManager;
31 import android.graphics.Color;
32 import android.graphics.drawable.ColorDrawable;
33 import android.graphics.drawable.Drawable;
34 import android.net.Uri;
35 import android.net.wifi.WifiManager;
36 import android.os.Binder;
37 import android.os.Bundle;
38 import android.os.UserManager;
39 import android.text.TextUtils;
40 import android.util.EventLog;
41 import android.util.Log;
42 
43 import androidx.annotation.Nullable;
44 import androidx.annotation.VisibleForTesting;
45 import androidx.core.graphics.drawable.IconCompat;
46 import androidx.slice.Slice;
47 import androidx.slice.builders.ListBuilder;
48 import androidx.slice.builders.SliceAction;
49 
50 import com.android.settings.R;
51 import com.android.settings.SubSettings;
52 import com.android.settings.Utils;
53 import com.android.settings.core.SubSettingLauncher;
54 import com.android.settings.network.NetworkProviderSettings;
55 import com.android.settings.network.SatelliteRepository;
56 import com.android.settings.network.WifiSwitchPreferenceController;
57 import com.android.settings.slices.CustomSliceable;
58 import com.android.settings.slices.SliceBackgroundWorker;
59 import com.android.settings.slices.SliceBuilderUtils;
60 import com.android.settings.wifi.AppStateChangeWifiStateBridge;
61 import com.android.settings.wifi.WifiDialogActivity;
62 import com.android.settings.wifi.WifiUtils;
63 import com.android.settings.wifi.details.WifiNetworkDetailsFragment;
64 import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils;
65 import com.android.wifitrackerlib.WifiEntry;
66 
67 import java.util.Arrays;
68 import java.util.List;
69 import java.util.Set;
70 import java.util.concurrent.ExecutionException;
71 import java.util.concurrent.Executors;
72 import java.util.concurrent.TimeUnit;
73 import java.util.concurrent.TimeoutException;
74 import java.util.stream.Collectors;
75 
76 /**
77  * {@link CustomSliceable} for Wi-Fi, used by generic clients.
78  *
79  * @deprecated This is not used after V and will be removed.
80  */
81 @Deprecated(forRemoval = true)
82 public class WifiSlice implements CustomSliceable {
83 
84     @VisibleForTesting
85     static final int DEFAULT_EXPANDED_ROW_COUNT = 3;
86     private static final String TAG = "WifiSlice";
87 
88     protected final Context mContext;
89     protected final WifiManager mWifiManager;
90     protected final WifiRestriction mWifiRestriction;
91 
WifiSlice(Context context)92     public WifiSlice(Context context) {
93         this(context, new WifiRestriction());
94     }
95 
96     @VisibleForTesting
WifiSlice(Context context, WifiRestriction wifiRestriction)97     WifiSlice(Context context, WifiRestriction wifiRestriction) {
98         mContext = context;
99         mWifiManager = mContext.getSystemService(WifiManager.class);
100         mWifiRestriction = wifiRestriction;
101     }
102 
103     @Override
getUri()104     public Uri getUri() {
105         return WIFI_SLICE_URI;
106     }
107 
108     @Override
getSlice()109     public Slice getSlice() {
110         final boolean isWifiEnabled = isWifiEnabled();
111         // If user is a guest just return a slice without a toggle.
112         if (isGuestUser(mContext)) {
113             Log.e(TAG, "Guest user is not allowed to configure Wi-Fi!");
114             EventLog.writeEvent(0x534e4554, "232798363", -1 /* UID */, "User is a guest");
115             return getListBuilder(isWifiEnabled, null /* wifiSliceItem */,
116                     false /* isWiFiPermissionGranted */).build();
117         }
118 
119         // If external calling package doesn't have Wi-Fi permission.
120         final boolean isPermissionGranted =
121                 isCallerExemptUid(mContext) || isPermissionGranted(mContext);
122         ListBuilder listBuilder = getListBuilder(isWifiEnabled, null /* wifiSliceItem */,
123                 isPermissionGranted);
124         // If the caller doesn't have the permission granted, just return a slice without a toggle.
125         if (!isWifiEnabled || !isPermissionGranted) {
126             return listBuilder.build();
127         }
128 
129         final WifiScanWorker worker = SliceBackgroundWorker.getInstance(getUri());
130         final List<WifiSliceItem> apList = worker != null ? worker.getResults() : null;
131         final int apCount = apList == null ? 0 : apList.size();
132         final boolean isFirstApActive = apCount > 0
133                 && apList.get(0).getConnectedState() != WifiEntry.CONNECTED_STATE_DISCONNECTED;
134 
135         if (isFirstApActive) {
136             // refresh header subtext
137             listBuilder = getListBuilder(
138                     true /* isWifiEnabled */, apList.get(0), true /* isWiFiPermissionGranted */);
139         }
140 
141         if (isApRowCollapsed()) {
142             return listBuilder.build();
143         }
144 
145         // Add AP rows
146         final CharSequence placeholder = mContext.getText(R.string.summary_placeholder);
147         for (int i = 0; i < DEFAULT_EXPANDED_ROW_COUNT; i++) {
148             if (i < apCount) {
149                 listBuilder.addRow(getWifiSliceItemRow(apList.get(i)));
150             } else if (i == apCount) {
151                 listBuilder.addRow(getLoadingRow(placeholder));
152             } else {
153                 listBuilder.addRow(new ListBuilder.RowBuilder()
154                         .setTitle(placeholder)
155                         .setSubtitle(placeholder));
156             }
157         }
158         return listBuilder.build();
159     }
160 
isGuestUser(Context context)161     protected static boolean isGuestUser(Context context) {
162         if (context == null) return false;
163         final UserManager userManager = context.getSystemService(UserManager.class);
164         if (userManager == null) return false;
165         return userManager.isGuestUser();
166     }
167 
isCallerExemptUid(Context context)168     private boolean isCallerExemptUid(Context context) {
169         final String[] allowedUidNames = context.getResources().getStringArray(
170                 R.array.config_exempt_wifi_permission_uid_name);
171         final String uidName =
172                 context.getPackageManager().getNameForUid(Binder.getCallingUid());
173         Log.d(TAG, "calling uid name : " + uidName);
174 
175         for (String allowedUidName : allowedUidNames) {
176             if (TextUtils.equals(uidName, allowedUidName)) {
177                 return true;
178             }
179         }
180         return false;
181     }
182 
isPermissionGranted(Context settingsContext)183     private static boolean isPermissionGranted(Context settingsContext) {
184         final int callingUid = Binder.getCallingUid();
185         final String callingPackage = settingsContext.getPackageManager()
186                 .getPackagesForUid(callingUid)[0];
187 
188         Context packageContext;
189         try {
190             packageContext = settingsContext.createPackageContext(callingPackage, 0);
191         } catch (PackageManager.NameNotFoundException e) {
192             Log.e(TAG, "Cannot create Context for package: " + callingPackage);
193             return false;
194         }
195 
196         // If app doesn't have related Wi-Fi permission, they shouldn't show Wi-Fi slice.
197         final boolean hasPermission = packageContext.checkPermission(
198                 android.Manifest.permission.CHANGE_WIFI_STATE, Binder.getCallingPid(),
199                 callingUid) == PackageManager.PERMISSION_GRANTED;
200         AppStateChangeWifiStateBridge.WifiSettingsState state =
201                 new AppStateChangeWifiStateBridge(settingsContext, null, null)
202                         .getWifiSettingsInfo(callingPackage, callingUid);
203 
204         return hasPermission && state.isPermissible();
205     }
206 
isApRowCollapsed()207     protected boolean isApRowCollapsed() {
208         return false;
209     }
210 
getHeaderRow(boolean isWifiEnabled, WifiSliceItem wifiSliceItem)211     protected ListBuilder.RowBuilder getHeaderRow(boolean isWifiEnabled,
212             WifiSliceItem wifiSliceItem) {
213         final IconCompat icon = IconCompat.createWithResource(mContext,
214                 R.drawable.ic_settings_wireless);
215         final String title = mContext.getString(R.string.wifi_settings);
216         final PendingIntent primaryAction = getPrimaryAction();
217         final SliceAction primarySliceAction = SliceAction.createDeeplink(primaryAction, icon,
218                 ListBuilder.ICON_IMAGE, title);
219 
220         final ListBuilder.RowBuilder builder = new ListBuilder.RowBuilder()
221                 .setTitle(title)
222                 .setPrimaryAction(primarySliceAction);
223 
224         if (!mWifiRestriction.isChangeWifiStateAllowed(mContext)) {
225             builder.setSubtitle(mContext.getString(R.string.not_allowed_by_ent));
226         }
227         return builder;
228     }
229 
getListBuilder(boolean isWifiEnabled, WifiSliceItem wifiSliceItem, boolean isWiFiPermissionGranted)230     private ListBuilder getListBuilder(boolean isWifiEnabled, WifiSliceItem wifiSliceItem,
231             boolean isWiFiPermissionGranted) {
232         final ListBuilder builder = new ListBuilder(mContext, getUri(), ListBuilder.INFINITY)
233                 .setAccentColor(COLOR_NOT_TINTED)
234                 .setKeywords(getKeywords())
235                 .addRow(getHeaderRow(isWifiEnabled, wifiSliceItem));
236         if (!isWiFiPermissionGranted || !mWifiRestriction.isChangeWifiStateAllowed(mContext)
237                 || isSatelliteOn()) {
238             return builder;
239         }
240 
241         final PendingIntent toggleAction = getBroadcastIntent(mContext);
242         final SliceAction toggleSliceAction = SliceAction.createToggle(toggleAction,
243                 null /* actionTitle */, isWifiEnabled);
244         builder.addAction(toggleSliceAction);
245 
246         return builder;
247     }
248 
getWifiSliceItemRow(WifiSliceItem wifiSliceItem)249     protected ListBuilder.RowBuilder getWifiSliceItemRow(WifiSliceItem wifiSliceItem) {
250         final CharSequence title = wifiSliceItem.getTitle();
251         final IconCompat levelIcon = getWifiSliceItemLevelIcon(wifiSliceItem);
252         final ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder()
253                 .setTitleItem(levelIcon, ListBuilder.ICON_IMAGE)
254                 .setTitle(title)
255                 .setSubtitle(wifiSliceItem.getSummary())
256                 .setContentDescription(wifiSliceItem.getContentDescription())
257                 .setPrimaryAction(getWifiEntryAction(wifiSliceItem, levelIcon, title));
258 
259         final IconCompat endIcon = getEndIcon(wifiSliceItem);
260         if (endIcon != null) {
261             rowBuilder.addEndItem(endIcon, ListBuilder.ICON_IMAGE);
262         }
263         return rowBuilder;
264     }
265 
getWifiSliceItemLevelIcon(WifiSliceItem wifiSliceItem)266     protected IconCompat getWifiSliceItemLevelIcon(WifiSliceItem wifiSliceItem) {
267         final @ColorInt int tint;
268         if (wifiSliceItem.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED) {
269             tint = Utils.getColorAccentDefaultColor(mContext);
270         } else if (wifiSliceItem.getConnectedState() == WifiEntry.CONNECTED_STATE_DISCONNECTED) {
271             tint = Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorControlNormal);
272         } else {
273             tint = Utils.getDisabled(mContext, Utils.getColorAttrDefaultColor(mContext,
274                     android.R.attr.colorControlNormal));
275         }
276 
277         Drawable drawable = mContext.getDrawable(getWifiIconResId(wifiSliceItem));
278         drawable.setTint(tint);
279         return Utils.createIconWithDrawable(drawable);
280     }
281 
282     @VisibleForTesting
getWifiIconResId(WifiSliceItem wifiSliceItem)283     int getWifiIconResId(WifiSliceItem wifiSliceItem) {
284         return (wifiSliceItem.isInstantHotspotNetwork())
285                 ? getHotspotIconResource(wifiSliceItem.getInstantHotspotDeviceType())
286                 : WifiUtils.getInternetIconResource(wifiSliceItem.getLevel(),
287                         wifiSliceItem.shouldShowXLevelIcon());
288     }
289 
getEndIcon(WifiSliceItem wifiSliceItem)290     protected IconCompat getEndIcon(WifiSliceItem wifiSliceItem) {
291         if (wifiSliceItem.getConnectedState() != WifiEntry.CONNECTED_STATE_DISCONNECTED) {
292             return IconCompat.createWithResource(mContext, R.drawable.ic_settings_24dp);
293         }
294 
295         if (wifiSliceItem.getSecurity() != WifiEntry.SECURITY_NONE) {
296             return IconCompat.createWithResource(mContext, R.drawable.ic_friction_lock_closed);
297         }
298         return null;
299     }
300 
getWifiEntryAction(WifiSliceItem wifiSliceItem, IconCompat icon, CharSequence title)301     protected SliceAction getWifiEntryAction(WifiSliceItem wifiSliceItem, IconCompat icon,
302             CharSequence title) {
303         final int requestCode = wifiSliceItem.getKey().hashCode();
304 
305         if (wifiSliceItem.getConnectedState() != WifiEntry.CONNECTED_STATE_DISCONNECTED) {
306             final Bundle bundle = new Bundle();
307             bundle.putString(WifiNetworkDetailsFragment.KEY_CHOSEN_WIFIENTRY_KEY,
308                     wifiSliceItem.getKey());
309             final Intent intent = new SubSettingLauncher(mContext)
310                     .setTitleRes(R.string.pref_title_network_details)
311                     .setDestination(WifiNetworkDetailsFragment.class.getName())
312                     .setArguments(bundle)
313                     .setSourceMetricsCategory(SettingsEnums.WIFI)
314                     .toIntent();
315             return getActivityAction(requestCode, intent, icon, title);
316         }
317 
318         if (wifiSliceItem.shouldEditBeforeConnect()) {
319             final Intent intent = new Intent(mContext, WifiDialogActivity.class)
320                     .putExtra(WifiDialogActivity.KEY_CHOSEN_WIFIENTRY_KEY, wifiSliceItem.getKey());
321             return getActivityAction(requestCode, intent, icon, title);
322         }
323 
324         final Intent intent = new Intent(mContext, ConnectToWifiHandler.class)
325                 .putExtra(ConnectToWifiHandler.KEY_CHOSEN_WIFIENTRY_KEY, wifiSliceItem.getKey())
326                 .putExtra(ConnectToWifiHandler.KEY_WIFI_SLICE_URI, getUri());
327         return getBroadcastAction(requestCode, intent, icon, title);
328     }
329 
getActivityAction(int requestCode, Intent intent, IconCompat icon, CharSequence title)330     private SliceAction getActivityAction(int requestCode, Intent intent, IconCompat icon,
331             CharSequence title) {
332         final PendingIntent pi = PendingIntent.getActivity(mContext, requestCode, intent,
333                 PendingIntent.FLAG_IMMUTABLE /* flags */);
334         return SliceAction.createDeeplink(pi, icon, ListBuilder.ICON_IMAGE, title);
335     }
336 
getBroadcastAction(int requestCode, Intent intent, IconCompat icon, CharSequence title)337     private SliceAction getBroadcastAction(int requestCode, Intent intent, IconCompat icon,
338             CharSequence title) {
339         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
340         final PendingIntent pi = PendingIntent.getBroadcast(mContext, requestCode, intent,
341                 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
342         return SliceAction.create(pi, icon, ListBuilder.ICON_IMAGE, title);
343     }
344 
getLoadingRow(CharSequence placeholder)345     private ListBuilder.RowBuilder getLoadingRow(CharSequence placeholder) {
346         final CharSequence title = mContext.getText(R.string.wifi_empty_list_wifi_on);
347 
348         // for aligning to the Wi-Fi AP's name
349         final IconCompat emptyIcon = Utils.createIconWithDrawable(
350                 new ColorDrawable(Color.TRANSPARENT));
351 
352         return new ListBuilder.RowBuilder()
353                 .setTitleItem(emptyIcon, ListBuilder.ICON_IMAGE)
354                 .setTitle(placeholder)
355                 .setSubtitle(title);
356     }
357 
358     /**
359      * Update the current wifi status to the boolean value keyed by
360      * {@link android.app.slice.Slice#EXTRA_TOGGLE_STATE} on {@param intent}.
361      */
362     @Override
onNotifyChange(Intent intent)363     public void onNotifyChange(Intent intent) {
364         final boolean newState = intent.getBooleanExtra(EXTRA_TOGGLE_STATE,
365                 mWifiManager.isWifiEnabled());
366         mWifiManager.setWifiEnabled(newState);
367         // Do not notifyChange on Uri. The service takes longer to update the current value than it
368         // does for the Slice to check the current value again. Let {@link WifiScanWorker}
369         // handle it.
370     }
371 
372     @Override
getIntent()373     public Intent getIntent() {
374         final String screenTitle = mContext.getText(R.string.wifi_settings).toString();
375         final Uri contentUri = new Uri.Builder().appendPath(KEY_WIFI).build();
376         final String className = NetworkProviderSettings.class.getName();
377         final String key = WifiSwitchPreferenceController.KEY;
378 
379         final Intent intent = SliceBuilderUtils.buildSearchResultPageIntent(mContext, className,
380                         key, screenTitle, SettingsEnums.DIALOG_WIFI_AP_EDIT, this)
381                 .setClassName(mContext.getPackageName(), SubSettings.class.getName())
382                 .setData(contentUri);
383 
384         return intent;
385     }
386 
387     @Override
getSliceHighlightMenuRes()388     public int getSliceHighlightMenuRes() {
389         return R.string.menu_key_network;
390     }
391 
isWifiEnabled()392     private boolean isWifiEnabled() {
393         switch (mWifiManager.getWifiState()) {
394             case WifiManager.WIFI_STATE_ENABLED:
395             case WifiManager.WIFI_STATE_ENABLING:
396                 return true;
397             default:
398                 return false;
399         }
400     }
401 
getPrimaryAction()402     private PendingIntent getPrimaryAction() {
403         final Intent intent = getIntent();
404         return PendingIntent.getActivity(mContext, 0 /* requestCode */,
405                 intent, PendingIntent.FLAG_IMMUTABLE /* flags */);
406     }
407 
getKeywords()408     private Set<String> getKeywords() {
409         final String keywords = mContext.getString(R.string.keywords_wifi);
410         return Arrays.asList(TextUtils.split(keywords, ","))
411                 .stream()
412                 .map(String::trim)
413                 .collect(Collectors.toSet());
414     }
415 
416     @Override
getBackgroundWorkerClass()417     public Class getBackgroundWorkerClass() {
418         return WifiScanWorker.class;
419     }
420 
421     @VisibleForTesting
422     static class WifiRestriction {
isChangeWifiStateAllowed(@ullable Context context)423         public boolean isChangeWifiStateAllowed(@Nullable Context context) {
424             if (context == null) return true;
425             return WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(context);
426         }
427     }
428 
isSatelliteOn()429     private boolean isSatelliteOn() {
430         SatelliteRepository satelliteRepository = new SatelliteRepository(mContext);
431         boolean isSatelliteOn = false;
432         try {
433             isSatelliteOn =
434                     satelliteRepository.requestIsEnabled(Executors.newSingleThreadExecutor())
435                             .get(2000, TimeUnit.MILLISECONDS);
436         } catch (ExecutionException | TimeoutException | InterruptedException e) {
437             Log.e(TAG, "Error to get satellite status : " + e);
438         }
439         return isSatelliteOn;
440     }
441 }
442