From 821608c5be06ebbda5857c771a483b8276ccfbbd Mon Sep 17 00:00:00 2001 From: Tsung-Mao Fang Date: Mon, 11 Apr 2022 18:35:25 +0800 Subject: [PATCH] Do not expose wifi slice when no permission Prior to this cl, slice provider always exposes wifi slice to calling package without confirming any wifi permissions. For current solution, we will check calling package's permission state and decide whether slice provider should expose wifi slice or not. Because settings search is a part of settings app, this permission checker won't be applied to settings intelligence. Test: manual, robotest, cts Also run manul Bug: 178014725 Change-Id: I2770b5b43366a5aa65c7519efc4243d350a21b26 --- .../settings/wifi/slice/WifiSlice.java | 40 ++++++++++++-- .../testutils/shadow/ShadowWifiSlice.java | 39 ++++++++++++++ .../wifi/slice/ContextualWifiSliceTest.java | 14 ++++- .../settings/wifi/slice/WifiSliceTest.java | 52 ++++++++++++++----- 4 files changed, 128 insertions(+), 17 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/testutils/shadow/ShadowWifiSlice.java diff --git a/src/com/android/settings/wifi/slice/WifiSlice.java b/src/com/android/settings/wifi/slice/WifiSlice.java index 743c7f9815a..f59dc60d6e0 100644 --- a/src/com/android/settings/wifi/slice/WifiSlice.java +++ b/src/com/android/settings/wifi/slice/WifiSlice.java @@ -26,13 +26,16 @@ 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.text.TextUtils; +import android.util.Log; import androidx.annotation.VisibleForTesting; import androidx.core.graphics.drawable.IconCompat; @@ -49,8 +52,8 @@ 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.WifiSettings; import com.android.settings.wifi.WifiUtils; import com.android.settings.wifi.details.WifiNetworkDetailsFragment; import com.android.wifitrackerlib.WifiEntry; @@ -67,6 +70,7 @@ 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; @@ -83,6 +87,12 @@ public class WifiSlice implements CustomSliceable { @Override public Slice getSlice() { + // If external calling package doesn't have Wi-Fi permission. + if (!Utils.isSettingsIntelligence(mContext) && !isPermissionGranted(mContext)) { + Log.i(TAG, "No wifi permissions to control wifi slice."); + return null; + } + final boolean isWifiEnabled = isWifiEnabled(); ListBuilder listBuilder = getListBuilder(isWifiEnabled, null /* wifiSliceItem */); if (!isWifiEnabled) { @@ -120,6 +130,30 @@ public class WifiSlice implements CustomSliceable { return listBuilder.build(); } + 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; } @@ -175,7 +209,7 @@ public class WifiSlice implements CustomSliceable { tint = Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorControlNormal); } else { tint = Utils.getDisabled(mContext, Utils.getColorAttrDefaultColor(mContext, - android.R.attr.colorControlNormal)); + android.R.attr.colorControlNormal)); } final Drawable drawable = mContext.getDrawable( @@ -275,7 +309,7 @@ public class WifiSlice implements CustomSliceable { final String key = WifiSwitchPreferenceController.KEY; final Intent intent = SliceBuilderUtils.buildSearchResultPageIntent(mContext, className, - key, screenTitle, SettingsEnums.DIALOG_WIFI_AP_EDIT, this) + key, screenTitle, SettingsEnums.DIALOG_WIFI_AP_EDIT, this) .setClassName(mContext.getPackageName(), SubSettings.class.getName()) .setData(contentUri); diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowWifiSlice.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowWifiSlice.java new file mode 100644 index 00000000000..ed583a4f0fe --- /dev/null +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowWifiSlice.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 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.testutils.shadow; + +import android.content.Context; + +import com.android.settings.wifi.slice.WifiSlice; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(WifiSlice.class) +public class ShadowWifiSlice { + + private static boolean sIsWifiPermissible; + + @Implementation + protected static boolean isPermissionGranted(Context settingsContext) { + return sIsWifiPermissible; + } + + public static void setWifiPermissible(boolean isWifiPermissible) { + sIsWifiPermissible = isWifiPermissible; + } +} diff --git a/tests/robotests/src/com/android/settings/wifi/slice/ContextualWifiSliceTest.java b/tests/robotests/src/com/android/settings/wifi/slice/ContextualWifiSliceTest.java index f31c216d260..52dcb5282da 100644 --- a/tests/robotests/src/com/android/settings/wifi/slice/ContextualWifiSliceTest.java +++ b/tests/robotests/src/com/android/settings/wifi/slice/ContextualWifiSliceTest.java @@ -24,9 +24,11 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; import android.content.ContentResolver; import android.content.Context; +import android.content.pm.PackageManager; import android.net.Network; import android.net.NetworkCapabilities; import android.net.wifi.WifiInfo; @@ -44,6 +46,7 @@ import com.android.settings.slices.CustomSliceRegistry; import com.android.settings.slices.SlicesFeatureProviderImpl; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.shadow.ShadowConnectivityManager; +import com.android.settings.testutils.shadow.ShadowWifiSlice; import org.junit.Before; import org.junit.Test; @@ -53,17 +56,20 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowBinder; import java.util.List; @RunWith(RobolectricTestRunner.class) -@Config(shadows = ShadowConnectivityManager.class) +@Config(shadows = {ShadowConnectivityManager.class, ShadowWifiSlice.class}) public class ContextualWifiSliceTest { private static final String SSID = "123"; @Mock private WifiManager mWifiManager; @Mock + private PackageManager mPackageManager; + @Mock private WifiInfo mWifiInfo; @Mock private Network mNetwork; @@ -88,10 +94,16 @@ public class ContextualWifiSliceTest { doReturn(mWifiInfo).when(mWifiManager).getConnectionInfo(); doReturn(SSID).when(mWifiInfo).getSSID(); doReturn(mNetwork).when(mWifiManager).getCurrentNetwork(); + when(mContext.getPackageManager()).thenReturn(mPackageManager); // Set-up specs for SliceMetadata. SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); + final String siPackageName = + mContext.getString(R.string.config_settingsintelligence_package_name); + ShadowBinder.setCallingUid(1); + when(mPackageManager.getPackagesForUid(1)).thenReturn(new String[]{siPackageName}); + ShadowWifiSlice.setWifiPermissible(true); mWifiSlice = new ContextualWifiSlice(mContext); } diff --git a/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java b/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java index b4a8b05f648..5b7a7d6c33f 100644 --- a/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java +++ b/tests/robotests/src/com/android/settings/wifi/slice/WifiSliceTest.java @@ -31,21 +31,20 @@ import static org.mockito.Mockito.when; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.net.Uri; import android.net.wifi.WifiManager; -import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; import androidx.slice.SliceItem; -import androidx.slice.SliceMetadata; import androidx.slice.SliceProvider; -import androidx.slice.core.SliceAction; import androidx.slice.core.SliceQuery; import androidx.slice.widget.SliceLiveData; import com.android.settings.R; import com.android.settings.slices.SliceBackgroundWorker; import com.android.settings.testutils.SliceTester; +import com.android.settings.testutils.shadow.ShadowWifiSlice; import com.android.wifitrackerlib.WifiEntry; import com.android.wifitrackerlib.WifiEntry.ConnectedState; @@ -59,24 +58,32 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowBinder; import java.util.ArrayList; import java.util.List; @RunWith(RobolectricTestRunner.class) -@Config(shadows = WifiSliceTest.ShadowSliceBackgroundWorker.class) +@Config(shadows = { + WifiSliceTest.ShadowSliceBackgroundWorker.class, + ShadowWifiSlice.class}) public class WifiSliceTest { private static final String AP1_NAME = "ap1"; private static final String AP2_NAME = "ap2"; private static final String AP3_NAME = "ap3"; + private static final int USER_ID = 1; @Mock private WifiManager mWifiManager; + @Mock + private PackageManager mPackageManager; + private Context mContext; private ContentResolver mResolver; private WifiSlice mWifiSlice; + private String mSIPackageName; @Before public void setUp() { @@ -86,27 +93,46 @@ public class WifiSliceTest { doReturn(mResolver).when(mContext).getContentResolver(); doReturn(mWifiManager).when(mContext).getSystemService(WifiManager.class); doReturn(WifiManager.WIFI_STATE_ENABLED).when(mWifiManager).getWifiState(); + when(mContext.getPackageManager()).thenReturn(mPackageManager); // Set-up specs for SliceMetadata. SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); + mSIPackageName = mContext.getString(R.string.config_settingsintelligence_package_name); + ShadowBinder.setCallingUid(USER_ID); + when(mPackageManager.getPackagesForUid(USER_ID)).thenReturn(new String[]{mSIPackageName}); + ShadowWifiSlice.setWifiPermissible(true); mWifiSlice = new WifiSlice(mContext); } @Test - public void getWifiSlice_shouldHaveTitleAndToggle() { + public void getWifiSlice_fromSIPackage_shouldHaveTitleAndToggle() { + when(mPackageManager.getPackagesForUid(USER_ID)).thenReturn(new String[]{mSIPackageName}); + ShadowWifiSlice.setWifiPermissible(false); + final Slice wifiSlice = mWifiSlice.getSlice(); - final SliceMetadata metadata = SliceMetadata.from(mContext, wifiSlice); - assertThat(metadata.getTitle()).isEqualTo(mContext.getString(R.string.wifi_settings)); + assertThat(wifiSlice).isNotNull(); + } - final List toggles = metadata.getToggles(); - assertThat(toggles).hasSize(1); + @Test + public void getWifiSlice_notFromSIPackageAndWithWifiPermission_shouldHaveTitleAndToggle() { + when(mPackageManager.getPackagesForUid(USER_ID)).thenReturn(new String[]{"com.test"}); + ShadowWifiSlice.setWifiPermissible(true); - final SliceAction primaryAction = metadata.getPrimaryAction(); - final IconCompat expectedToggleIcon = IconCompat.createWithResource(mContext, - R.drawable.ic_settings_wireless); - assertThat(primaryAction.getIcon().toString()).isEqualTo(expectedToggleIcon.toString()); + final Slice wifiSlice = mWifiSlice.getSlice(); + + assertThat(wifiSlice).isNotNull(); + } + + @Test + public void getWifiSlice_notFromSIPackageAndWithoutWifiPermission_shouldNoSlice() { + when(mPackageManager.getPackagesForUid(USER_ID)).thenReturn(new String[]{"com.test"}); + ShadowWifiSlice.setWifiPermissible(false); + + final Slice wifiSlice = mWifiSlice.getSlice(); + + assertThat(wifiSlice).isNull(); } @Test