Prior to this cl, we use #getPackagesForUid() to get a list of calling package names and pick up 1st package name in the list as target calling package. And then go to check the Wi-Fi permission. This implementation is ok for most apps without sharing system uid. However, this may not work if the caller is set as sharing system ui. In this case, we get a list of packages and we don't know which one is caller. So, if we decide to choose the 1st package of list as our calling package, then it could fail to pass permission check since that package could not a calling package. In this cl, we skip permission check for those packages running with system uid. So, it can resolve this Wi-Fi Panel problem since Wi-Fi panel running on settings process and also promise the security issue at the same time. Test: 1. adb shell am start -a android.settings.panel.action.WIFI 2. Verify on assistant app and system ui launcher and search app. Bug: 240531998 Change-Id: Ia825853dde2e966e3d390cecfbe1a99f6439d31e
413 lines
17 KiB
Java
413 lines
17 KiB
Java
/*
|
|
* Copyright (C) 2018 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.settings.wifi.slice;
|
|
|
|
import static android.app.slice.Slice.EXTRA_TOGGLE_STATE;
|
|
import static android.provider.SettingsSlicesContract.KEY_WIFI;
|
|
|
|
import static com.android.settings.slices.CustomSliceRegistry.WIFI_SLICE_URI;
|
|
|
|
import android.annotation.ColorInt;
|
|
import android.app.PendingIntent;
|
|
import android.app.settings.SettingsEnums;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.pm.PackageManager;
|
|
import android.graphics.Color;
|
|
import android.graphics.drawable.ColorDrawable;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.net.Uri;
|
|
import android.net.wifi.WifiManager;
|
|
import android.os.Binder;
|
|
import android.os.Bundle;
|
|
import android.os.UserManager;
|
|
import android.text.TextUtils;
|
|
import android.util.EventLog;
|
|
import android.util.Log;
|
|
|
|
import androidx.annotation.Nullable;
|
|
import androidx.annotation.VisibleForTesting;
|
|
import androidx.core.graphics.drawable.IconCompat;
|
|
import androidx.slice.Slice;
|
|
import androidx.slice.builders.ListBuilder;
|
|
import androidx.slice.builders.SliceAction;
|
|
|
|
import com.android.settings.R;
|
|
import com.android.settings.SubSettings;
|
|
import com.android.settings.Utils;
|
|
import com.android.settings.core.SubSettingLauncher;
|
|
import com.android.settings.network.NetworkProviderSettings;
|
|
import com.android.settings.network.WifiSwitchPreferenceController;
|
|
import com.android.settings.slices.CustomSliceable;
|
|
import com.android.settings.slices.SliceBackgroundWorker;
|
|
import com.android.settings.slices.SliceBuilderUtils;
|
|
import com.android.settings.wifi.AppStateChangeWifiStateBridge;
|
|
import com.android.settings.wifi.WifiDialogActivity;
|
|
import com.android.settings.wifi.WifiUtils;
|
|
import com.android.settings.wifi.details.WifiNetworkDetailsFragment;
|
|
import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils;
|
|
import com.android.wifitrackerlib.WifiEntry;
|
|
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import java.util.stream.Collectors;
|
|
|
|
/**
|
|
* {@link CustomSliceable} for Wi-Fi, used by generic clients.
|
|
*/
|
|
public class WifiSlice implements CustomSliceable {
|
|
|
|
@VisibleForTesting
|
|
static final int DEFAULT_EXPANDED_ROW_COUNT = 3;
|
|
private static final String TAG = "WifiSlice";
|
|
|
|
protected final Context mContext;
|
|
protected final WifiManager mWifiManager;
|
|
protected final WifiRestriction mWifiRestriction;
|
|
|
|
public WifiSlice(Context context) {
|
|
this(context, new WifiRestriction());
|
|
}
|
|
|
|
@VisibleForTesting
|
|
WifiSlice(Context context, WifiRestriction wifiRestriction) {
|
|
mContext = context;
|
|
mWifiManager = mContext.getSystemService(WifiManager.class);
|
|
mWifiRestriction = wifiRestriction;
|
|
}
|
|
|
|
@Override
|
|
public Uri getUri() {
|
|
return WIFI_SLICE_URI;
|
|
}
|
|
|
|
@Override
|
|
public Slice getSlice() {
|
|
final boolean isWifiEnabled = isWifiEnabled();
|
|
// If user is a guest just return a slice without a toggle.
|
|
if (isGuestUser(mContext)) {
|
|
Log.e(TAG, "Guest user is not allowed to configure Wi-Fi!");
|
|
EventLog.writeEvent(0x534e4554, "232798363", -1 /* UID */, "User is a guest");
|
|
return getListBuilder(isWifiEnabled, null /* wifiSliceItem */,
|
|
false /* isWiFiPermissionGranted */).build();
|
|
}
|
|
|
|
// If external calling package doesn't have Wi-Fi permission.
|
|
final boolean isPermissionGranted =
|
|
isCallerExemptUid(mContext) || isPermissionGranted(mContext);
|
|
ListBuilder listBuilder = getListBuilder(isWifiEnabled, null /* wifiSliceItem */,
|
|
isPermissionGranted);
|
|
// If the caller doesn't have the permission granted, just return a slice without a toggle.
|
|
if (!isWifiEnabled || !isPermissionGranted) {
|
|
return listBuilder.build();
|
|
}
|
|
|
|
final WifiScanWorker worker = SliceBackgroundWorker.getInstance(getUri());
|
|
final List<WifiSliceItem> apList = worker != null ? worker.getResults() : null;
|
|
final int apCount = apList == null ? 0 : apList.size();
|
|
final boolean isFirstApActive = apCount > 0
|
|
&& apList.get(0).getConnectedState() != WifiEntry.CONNECTED_STATE_DISCONNECTED;
|
|
|
|
if (isFirstApActive) {
|
|
// refresh header subtext
|
|
listBuilder = getListBuilder(
|
|
true /* isWifiEnabled */, apList.get(0), true /* isWiFiPermissionGranted */);
|
|
}
|
|
|
|
if (isApRowCollapsed()) {
|
|
return listBuilder.build();
|
|
}
|
|
|
|
// Add AP rows
|
|
final CharSequence placeholder = mContext.getText(R.string.summary_placeholder);
|
|
for (int i = 0; i < DEFAULT_EXPANDED_ROW_COUNT; i++) {
|
|
if (i < apCount) {
|
|
listBuilder.addRow(getWifiSliceItemRow(apList.get(i)));
|
|
} else if (i == apCount) {
|
|
listBuilder.addRow(getLoadingRow(placeholder));
|
|
} else {
|
|
listBuilder.addRow(new ListBuilder.RowBuilder()
|
|
.setTitle(placeholder)
|
|
.setSubtitle(placeholder));
|
|
}
|
|
}
|
|
return listBuilder.build();
|
|
}
|
|
|
|
protected static boolean isGuestUser(Context context) {
|
|
if (context == null) return false;
|
|
final UserManager userManager = context.getSystemService(UserManager.class);
|
|
if (userManager == null) return false;
|
|
return userManager.isGuestUser();
|
|
}
|
|
|
|
private boolean isCallerExemptUid(Context context) {
|
|
final String[] allowedUidNames = context.getResources().getStringArray(
|
|
R.array.config_exempt_wifi_permission_uid_name);
|
|
final String uidName =
|
|
context.getPackageManager().getNameForUid(Binder.getCallingUid());
|
|
Log.d(TAG, "calling uid name : " + uidName);
|
|
|
|
for (String allowedUidName : allowedUidNames) {
|
|
if (TextUtils.equals(uidName, allowedUidName)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static boolean isPermissionGranted(Context settingsContext) {
|
|
final int callingUid = Binder.getCallingUid();
|
|
final String callingPackage = settingsContext.getPackageManager()
|
|
.getPackagesForUid(callingUid)[0];
|
|
|
|
Context packageContext;
|
|
try {
|
|
packageContext = settingsContext.createPackageContext(callingPackage, 0);
|
|
} catch (PackageManager.NameNotFoundException e) {
|
|
Log.e(TAG, "Cannot create Context for package: " + callingPackage);
|
|
return false;
|
|
}
|
|
|
|
// If app doesn't have related Wi-Fi permission, they shouldn't show Wi-Fi slice.
|
|
final boolean hasPermission = packageContext.checkPermission(
|
|
android.Manifest.permission.CHANGE_WIFI_STATE, Binder.getCallingPid(),
|
|
callingUid) == PackageManager.PERMISSION_GRANTED;
|
|
AppStateChangeWifiStateBridge.WifiSettingsState state =
|
|
new AppStateChangeWifiStateBridge(settingsContext, null, null)
|
|
.getWifiSettingsInfo(callingPackage, callingUid);
|
|
|
|
return hasPermission && state.isPermissible();
|
|
}
|
|
|
|
protected boolean isApRowCollapsed() {
|
|
return false;
|
|
}
|
|
|
|
protected ListBuilder.RowBuilder getHeaderRow(boolean isWifiEnabled,
|
|
WifiSliceItem wifiSliceItem) {
|
|
final IconCompat icon = IconCompat.createWithResource(mContext,
|
|
R.drawable.ic_settings_wireless);
|
|
final String title = mContext.getString(R.string.wifi_settings);
|
|
final PendingIntent primaryAction = getPrimaryAction();
|
|
final SliceAction primarySliceAction = SliceAction.createDeeplink(primaryAction, icon,
|
|
ListBuilder.ICON_IMAGE, title);
|
|
|
|
final ListBuilder.RowBuilder builder = new ListBuilder.RowBuilder()
|
|
.setTitle(title)
|
|
.setPrimaryAction(primarySliceAction);
|
|
|
|
if (!mWifiRestriction.isChangeWifiStateAllowed(mContext)) {
|
|
builder.setSubtitle(mContext.getString(R.string.not_allowed_by_ent));
|
|
}
|
|
return builder;
|
|
}
|
|
|
|
private ListBuilder getListBuilder(boolean isWifiEnabled, WifiSliceItem wifiSliceItem,
|
|
boolean isWiFiPermissionGranted) {
|
|
final ListBuilder builder = new ListBuilder(mContext, getUri(), ListBuilder.INFINITY)
|
|
.setAccentColor(COLOR_NOT_TINTED)
|
|
.setKeywords(getKeywords())
|
|
.addRow(getHeaderRow(isWifiEnabled, wifiSliceItem));
|
|
if (!isWiFiPermissionGranted || !mWifiRestriction.isChangeWifiStateAllowed(mContext)) {
|
|
return builder;
|
|
}
|
|
|
|
final PendingIntent toggleAction = getBroadcastIntent(mContext);
|
|
final SliceAction toggleSliceAction = SliceAction.createToggle(toggleAction,
|
|
null /* actionTitle */, isWifiEnabled);
|
|
builder.addAction(toggleSliceAction);
|
|
|
|
return builder;
|
|
}
|
|
|
|
protected ListBuilder.RowBuilder getWifiSliceItemRow(WifiSliceItem wifiSliceItem) {
|
|
final CharSequence title = wifiSliceItem.getTitle();
|
|
final IconCompat levelIcon = getWifiSliceItemLevelIcon(wifiSliceItem);
|
|
final ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder()
|
|
.setTitleItem(levelIcon, ListBuilder.ICON_IMAGE)
|
|
.setTitle(title)
|
|
.setSubtitle(wifiSliceItem.getSummary())
|
|
.setContentDescription(wifiSliceItem.getContentDescription())
|
|
.setPrimaryAction(getWifiEntryAction(wifiSliceItem, levelIcon, title));
|
|
|
|
final IconCompat endIcon = getEndIcon(wifiSliceItem);
|
|
if (endIcon != null) {
|
|
rowBuilder.addEndItem(endIcon, ListBuilder.ICON_IMAGE);
|
|
}
|
|
return rowBuilder;
|
|
}
|
|
|
|
protected IconCompat getWifiSliceItemLevelIcon(WifiSliceItem wifiSliceItem) {
|
|
final @ColorInt int tint;
|
|
if (wifiSliceItem.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED) {
|
|
tint = Utils.getColorAccentDefaultColor(mContext);
|
|
} else if (wifiSliceItem.getConnectedState() == WifiEntry.CONNECTED_STATE_DISCONNECTED) {
|
|
tint = Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorControlNormal);
|
|
} else {
|
|
tint = Utils.getDisabled(mContext, Utils.getColorAttrDefaultColor(mContext,
|
|
android.R.attr.colorControlNormal));
|
|
}
|
|
|
|
final Drawable drawable = mContext.getDrawable(
|
|
WifiUtils.getInternetIconResource(wifiSliceItem.getLevel(),
|
|
wifiSliceItem.shouldShowXLevelIcon()));
|
|
drawable.setTint(tint);
|
|
return Utils.createIconWithDrawable(drawable);
|
|
}
|
|
|
|
protected IconCompat getEndIcon(WifiSliceItem wifiSliceItem) {
|
|
if (wifiSliceItem.getConnectedState() != WifiEntry.CONNECTED_STATE_DISCONNECTED) {
|
|
return IconCompat.createWithResource(mContext, R.drawable.ic_settings_24dp);
|
|
}
|
|
|
|
if (wifiSliceItem.getSecurity() != WifiEntry.SECURITY_NONE) {
|
|
return IconCompat.createWithResource(mContext, R.drawable.ic_friction_lock_closed);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
protected SliceAction getWifiEntryAction(WifiSliceItem wifiSliceItem, IconCompat icon,
|
|
CharSequence title) {
|
|
final int requestCode = wifiSliceItem.getKey().hashCode();
|
|
|
|
if (wifiSliceItem.getConnectedState() != WifiEntry.CONNECTED_STATE_DISCONNECTED) {
|
|
final Bundle bundle = new Bundle();
|
|
bundle.putString(WifiNetworkDetailsFragment.KEY_CHOSEN_WIFIENTRY_KEY,
|
|
wifiSliceItem.getKey());
|
|
final Intent intent = new SubSettingLauncher(mContext)
|
|
.setTitleRes(R.string.pref_title_network_details)
|
|
.setDestination(WifiNetworkDetailsFragment.class.getName())
|
|
.setArguments(bundle)
|
|
.setSourceMetricsCategory(SettingsEnums.WIFI)
|
|
.toIntent();
|
|
return getActivityAction(requestCode, intent, icon, title);
|
|
}
|
|
|
|
if (wifiSliceItem.shouldEditBeforeConnect()) {
|
|
final Intent intent = new Intent(mContext, WifiDialogActivity.class)
|
|
.putExtra(WifiDialogActivity.KEY_CHOSEN_WIFIENTRY_KEY, wifiSliceItem.getKey());
|
|
return getActivityAction(requestCode, intent, icon, title);
|
|
}
|
|
|
|
final Intent intent = new Intent(mContext, ConnectToWifiHandler.class)
|
|
.putExtra(ConnectToWifiHandler.KEY_CHOSEN_WIFIENTRY_KEY, wifiSliceItem.getKey())
|
|
.putExtra(ConnectToWifiHandler.KEY_WIFI_SLICE_URI, getUri());
|
|
return getBroadcastAction(requestCode, intent, icon, title);
|
|
}
|
|
|
|
private SliceAction getActivityAction(int requestCode, Intent intent, IconCompat icon,
|
|
CharSequence title) {
|
|
final PendingIntent pi = PendingIntent.getActivity(mContext, requestCode, intent,
|
|
PendingIntent.FLAG_IMMUTABLE /* flags */);
|
|
return SliceAction.createDeeplink(pi, icon, ListBuilder.ICON_IMAGE, title);
|
|
}
|
|
|
|
private SliceAction getBroadcastAction(int requestCode, Intent intent, IconCompat icon,
|
|
CharSequence title) {
|
|
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
|
|
final PendingIntent pi = PendingIntent.getBroadcast(mContext, requestCode, intent,
|
|
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
|
return SliceAction.create(pi, icon, ListBuilder.ICON_IMAGE, title);
|
|
}
|
|
|
|
private ListBuilder.RowBuilder getLoadingRow(CharSequence placeholder) {
|
|
final CharSequence title = mContext.getText(R.string.wifi_empty_list_wifi_on);
|
|
|
|
// for aligning to the Wi-Fi AP's name
|
|
final IconCompat emptyIcon = Utils.createIconWithDrawable(
|
|
new ColorDrawable(Color.TRANSPARENT));
|
|
|
|
return new ListBuilder.RowBuilder()
|
|
.setTitleItem(emptyIcon, ListBuilder.ICON_IMAGE)
|
|
.setTitle(placeholder)
|
|
.setSubtitle(title);
|
|
}
|
|
|
|
/**
|
|
* Update the current wifi status to the boolean value keyed by
|
|
* {@link android.app.slice.Slice#EXTRA_TOGGLE_STATE} on {@param intent}.
|
|
*/
|
|
@Override
|
|
public void onNotifyChange(Intent intent) {
|
|
final boolean newState = intent.getBooleanExtra(EXTRA_TOGGLE_STATE,
|
|
mWifiManager.isWifiEnabled());
|
|
mWifiManager.setWifiEnabled(newState);
|
|
// Do not notifyChange on Uri. The service takes longer to update the current value than it
|
|
// does for the Slice to check the current value again. Let {@link WifiScanWorker}
|
|
// handle it.
|
|
}
|
|
|
|
@Override
|
|
public Intent getIntent() {
|
|
final String screenTitle = mContext.getText(R.string.wifi_settings).toString();
|
|
final Uri contentUri = new Uri.Builder().appendPath(KEY_WIFI).build();
|
|
final String className = NetworkProviderSettings.class.getName();
|
|
final String key = WifiSwitchPreferenceController.KEY;
|
|
|
|
final Intent intent = SliceBuilderUtils.buildSearchResultPageIntent(mContext, className,
|
|
key, screenTitle, SettingsEnums.DIALOG_WIFI_AP_EDIT, this)
|
|
.setClassName(mContext.getPackageName(), SubSettings.class.getName())
|
|
.setData(contentUri);
|
|
|
|
return intent;
|
|
}
|
|
|
|
@Override
|
|
public int getSliceHighlightMenuRes() {
|
|
return R.string.menu_key_network;
|
|
}
|
|
|
|
private boolean isWifiEnabled() {
|
|
switch (mWifiManager.getWifiState()) {
|
|
case WifiManager.WIFI_STATE_ENABLED:
|
|
case WifiManager.WIFI_STATE_ENABLING:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private PendingIntent getPrimaryAction() {
|
|
final Intent intent = getIntent();
|
|
return PendingIntent.getActivity(mContext, 0 /* requestCode */,
|
|
intent, PendingIntent.FLAG_IMMUTABLE /* flags */);
|
|
}
|
|
|
|
private Set<String> getKeywords() {
|
|
final String keywords = mContext.getString(R.string.keywords_wifi);
|
|
return Arrays.asList(TextUtils.split(keywords, ","))
|
|
.stream()
|
|
.map(String::trim)
|
|
.collect(Collectors.toSet());
|
|
}
|
|
|
|
@Override
|
|
public Class getBackgroundWorkerClass() {
|
|
return WifiScanWorker.class;
|
|
}
|
|
|
|
@VisibleForTesting
|
|
static class WifiRestriction {
|
|
public boolean isChangeWifiStateAllowed(@Nullable Context context) {
|
|
if (context == null) return true;
|
|
return WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(context);
|
|
}
|
|
}
|
|
}
|