From 4493d84ab2cbd8c36674fcc4009081850b04a63c Mon Sep 17 00:00:00 2001 From: Tsung-Mao Fang Date: Wed, 14 Apr 2021 15:51:37 +0800 Subject: [PATCH 1/5] Prevent HTML Injection on the Device Admin request screen The root issue is that CharSequence is an interface. String implements that interface, however, Spanned class too which is a rich text format that can store HTML code. The solution is enforce to use String type which won't include any HTML function. Test: Rebuilt apk and see the string without HTML style. Bug: 179042963 Change-Id: I53b460b12da918e022d2f2934f114d205dbaadb0 Merged-In: I53b460b12da918e022d2f2934f114d205dbaadb0 (cherry picked from commit 0bf3c98b2f325f70d5492a7c7ade16951a802600) (cherry picked from commit 52f9039d5cc775a02dab90492cca98850a82872a) --- .../specialaccess/deviceadmin/DeviceAdminAdd.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminAdd.java b/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminAdd.java index 9afb2b4f2c1..113922e2303 100644 --- a/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminAdd.java +++ b/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminAdd.java @@ -102,7 +102,7 @@ public class DeviceAdminAdd extends Activity { DevicePolicyManager mDPM; AppOpsManager mAppOps; DeviceAdminInfo mDeviceAdmin; - CharSequence mAddMsgText; + String mAddMsgText; String mProfileOwnerName; ImageView mAdminIcon; @@ -274,7 +274,11 @@ public class DeviceAdminAdd extends Activity { } } - mAddMsgText = getIntent().getCharSequenceExtra(DevicePolicyManager.EXTRA_ADD_EXPLANATION); + final CharSequence addMsgCharSequence = getIntent().getCharSequenceExtra( + DevicePolicyManager.EXTRA_ADD_EXPLANATION); + if (addMsgCharSequence != null) { + mAddMsgText = addMsgCharSequence.toString(); + } if (mAddingProfileOwner) { // If we're trying to add a profile owner and user setup hasn't completed yet, no @@ -628,7 +632,7 @@ public class DeviceAdminAdd extends Activity { } catch (Resources.NotFoundException e) { mAdminDescription.setVisibility(View.GONE); } - if (mAddMsgText != null) { + if (!TextUtils.isEmpty(mAddMsgText)) { mAddMsg.setText(mAddMsgText); mAddMsg.setVisibility(View.VISIBLE); } else { From 42be5cfa1029ff3e3f451289b3368631ed77f4fb Mon Sep 17 00:00:00 2001 From: Jakub Pawlowski Date: Mon, 10 May 2021 19:26:13 +0200 Subject: [PATCH 2/5] Prevent drawing on top of DevicePickerActivity Bug: 182584940 Change-Id: I8b27b397bce1708a42b96b1b647c64e23142c468 (cherry picked from commit 68f799a4c6afc3b96b354d1b8b970780bb6cf58a) --- src/com/android/settings/bluetooth/DevicePickerActivity.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/com/android/settings/bluetooth/DevicePickerActivity.java b/src/com/android/settings/bluetooth/DevicePickerActivity.java index 77e8ba4e1c3..a4f025b267a 100644 --- a/src/com/android/settings/bluetooth/DevicePickerActivity.java +++ b/src/com/android/settings/bluetooth/DevicePickerActivity.java @@ -16,6 +16,8 @@ package com.android.settings.bluetooth; +import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; + import android.os.Bundle; import androidx.fragment.app.FragmentActivity; @@ -31,6 +33,7 @@ public final class DevicePickerActivity extends FragmentActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS); setContentView(R.layout.bluetooth_device_picker); } } From f1d1bb78162209335b086ee10d8b7449879bcc64 Mon Sep 17 00:00:00 2001 From: Hugh Chen Date: Fri, 12 Mar 2021 10:40:20 +0800 Subject: [PATCH 3/5] RESTRICT AUTOMERGE Fix bluetooth settings will broadcast to anywhere when some cases BluetoothPermissionActivity and DevicePickerFragment will send broadcast to return the result to calling apps. As this broadcast intent is from Settings with uid 1000, it will be sent to any protected BroadcastReceivers in the device. It can make an attacker send broadcast to protected BroadcastReceivers like factory reset intent (android/com.android.server.MasterClearReceiver) via BluetoothPermissionActivity or DevicePickerFragment. This CL will not allow to set package name and class name to avoid the attacker. Bug: 179386960 Bug: 179386068 Test: make -j42 RunSettingsRoboTests and use test apk to manually test to verify factory reset not started and no system UI notification. Change-Id: Id27a78091ab578077853b8fbb97a4422cff0a158 (cherry picked from commit 8adedc62496cf8cf6ecfc6ccf23b0b248081d7d4) (cherry picked from commit e21d06f6eeb20cdd0c6e2a995f27e942225fb807) --- .../BluetoothPermissionActivity.java | 19 +++--- .../bluetooth/BluetoothPermissionRequest.java | 21 ++----- .../bluetooth/DevicePickerFragment.java | 13 ++--- .../BluetoothPermissionActivityTest.java | 58 +++++++++++++++++++ .../bluetooth/DevicePickerFragmentTest.java | 31 +++++++++- 5 files changed, 107 insertions(+), 35 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/bluetooth/BluetoothPermissionActivityTest.java diff --git a/src/com/android/settings/bluetooth/BluetoothPermissionActivity.java b/src/com/android/settings/bluetooth/BluetoothPermissionActivity.java index e40e30dd0a0..0cca4a4ec51 100644 --- a/src/com/android/settings/bluetooth/BluetoothPermissionActivity.java +++ b/src/com/android/settings/bluetooth/BluetoothPermissionActivity.java @@ -16,6 +16,8 @@ package com.android.settings.bluetooth; +import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; + import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; @@ -30,12 +32,11 @@ import android.widget.TextView; import androidx.preference.Preference; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.AlertActivity; import com.android.internal.app.AlertController; import com.android.settings.R; -import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; - /** * BluetoothPermissionActivity shows a dialog for accepting incoming * profile connection request from untrusted devices. @@ -51,8 +52,6 @@ public class BluetoothPermissionActivity extends AlertActivity implements private TextView messageView; private Button mOkButton; private BluetoothDevice mDevice; - private String mReturnPackage = null; - private String mReturnClass = null; private int mRequestType = 0; private BroadcastReceiver mReceiver = new BroadcastReceiver() { @@ -89,8 +88,6 @@ public class BluetoothPermissionActivity extends AlertActivity implements } mDevice = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - mReturnPackage = i.getStringExtra(BluetoothDevice.EXTRA_PACKAGE_NAME); - mReturnClass = i.getStringExtra(BluetoothDevice.EXTRA_CLASS_NAME); mRequestType = i.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS); @@ -202,14 +199,14 @@ public class BluetoothPermissionActivity extends AlertActivity implements sendReplyIntentToReceiver(false, true); } - private void sendReplyIntentToReceiver(final boolean allowed, final boolean always) { + @VisibleForTesting + void sendReplyIntentToReceiver(final boolean allowed, final boolean always) { Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY); - if (mReturnPackage != null && mReturnClass != null) { - intent.setClassName(mReturnPackage, mReturnClass); + if (DEBUG) { + Log.i(TAG, "sendReplyIntentToReceiver() Request type: " + mRequestType + + " mReturnPackage"); } - if (DEBUG) Log.i(TAG, "sendReplyIntentToReceiver() Request type: " + mRequestType + - " mReturnPackage" + mReturnPackage + " mReturnClass" + mReturnClass); intent.putExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT, allowed ? BluetoothDevice.CONNECTION_ACCESS_YES diff --git a/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java b/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java index 177bd893558..de957a7eb91 100644 --- a/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java +++ b/src/com/android/settings/bluetooth/BluetoothPermissionRequest.java @@ -56,8 +56,6 @@ public final class BluetoothPermissionRequest extends BroadcastReceiver { Context mContext; int mRequestType; BluetoothDevice mDevice; - String mReturnPackage = null; - String mReturnClass = null; @Override public void onReceive(Context context, Intent intent) { @@ -77,11 +75,10 @@ public final class BluetoothPermissionRequest extends BroadcastReceiver { mDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); mRequestType = intent.getIntExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, BluetoothDevice.REQUEST_TYPE_PROFILE_CONNECTION); - mReturnPackage = intent.getStringExtra(BluetoothDevice.EXTRA_PACKAGE_NAME); - mReturnClass = intent.getStringExtra(BluetoothDevice.EXTRA_CLASS_NAME); - if (DEBUG) Log.d(TAG, "onReceive request type: " + mRequestType + " return " - + mReturnPackage + "," + mReturnClass); + if (DEBUG) { + Log.d(TAG, "onReceive request type: " + mRequestType); + } // Even if the user has already made the choice, Bluetooth still may not know that if // the user preference data have not been migrated from Settings app's shared @@ -110,8 +107,6 @@ public final class BluetoothPermissionRequest extends BroadcastReceiver { connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, mRequestType); connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); - connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_PACKAGE_NAME, mReturnPackage); - connectionAccessIntent.putExtra(BluetoothDevice.EXTRA_CLASS_NAME, mReturnClass); String deviceAddress = mDevice != null ? mDevice.getAddress() : null; String deviceName = mDevice != null ? mDevice.getName() : null; @@ -230,7 +225,7 @@ public final class BluetoothPermissionRequest extends BroadcastReceiver { LocalBluetoothManager bluetoothManager = Utils.getLocalBtManager(mContext); CachedBluetoothDeviceManager cachedDeviceManager = - bluetoothManager.getCachedDeviceManager(); + bluetoothManager.getCachedDeviceManager(); CachedBluetoothDevice cachedDevice = cachedDeviceManager.findDevice(mDevice); if (cachedDevice == null) { cachedDevice = cachedDeviceManager.addDevice(mDevice); @@ -288,13 +283,9 @@ public final class BluetoothPermissionRequest extends BroadcastReceiver { private void sendReplyIntentToReceiver(final boolean allowed) { Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY); - if (mReturnPackage != null && mReturnClass != null) { - intent.setClassName(mReturnPackage, mReturnClass); - } - intent.putExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT, - allowed ? BluetoothDevice.CONNECTION_ACCESS_YES - : BluetoothDevice.CONNECTION_ACCESS_NO); + allowed ? BluetoothDevice.CONNECTION_ACCESS_YES + : BluetoothDevice.CONNECTION_ACCESS_NO); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE, mRequestType); mContext.sendBroadcast(intent, android.Manifest.permission.BLUETOOTH_ADMIN); diff --git a/src/com/android/settings/bluetooth/DevicePickerFragment.java b/src/com/android/settings/bluetooth/DevicePickerFragment.java index ab8eea5db68..f60183b1c4f 100644 --- a/src/com/android/settings/bluetooth/DevicePickerFragment.java +++ b/src/com/android/settings/bluetooth/DevicePickerFragment.java @@ -48,10 +48,10 @@ public final class DevicePickerFragment extends DeviceListPreferenceFragment { @VisibleForTesting BluetoothProgressCategory mAvailableDevicesCategory; + @VisibleForTesting + Context mContext; private boolean mNeedAuth; - private String mLaunchPackage; - private String mLaunchClass; private boolean mScanAllowed; public DevicePickerFragment() { @@ -64,8 +64,6 @@ public final class DevicePickerFragment extends DeviceListPreferenceFragment { mNeedAuth = intent.getBooleanExtra(BluetoothDevicePicker.EXTRA_NEED_AUTH, false); setFilter(intent.getIntExtra(BluetoothDevicePicker.EXTRA_FILTER_TYPE, BluetoothDevicePicker.FILTER_TYPE_ALL)); - mLaunchPackage = intent.getStringExtra(BluetoothDevicePicker.EXTRA_LAUNCH_PACKAGE); - mLaunchClass = intent.getStringExtra(BluetoothDevicePicker.EXTRA_LAUNCH_CLASS); mAvailableDevicesCategory = (BluetoothProgressCategory) findPreference(KEY_BT_DEVICE_LIST); } @@ -85,6 +83,7 @@ public final class DevicePickerFragment extends DeviceListPreferenceFragment { getActivity().setTitle(getString(R.string.device_picker)); UserManager um = (UserManager) getSystemService(Context.USER_SERVICE); mScanAllowed = !um.hasUserRestriction(DISALLOW_CONFIG_BLUETOOTH); + mContext = getContext(); setHasOptionsMenu(true); } @@ -190,9 +189,7 @@ public final class DevicePickerFragment extends DeviceListPreferenceFragment { private void sendDevicePickedIntent(BluetoothDevice device) { Intent intent = new Intent(BluetoothDevicePicker.ACTION_DEVICE_SELECTED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); - if (mLaunchPackage != null && mLaunchClass != null) { - intent.setClassName(mLaunchPackage, mLaunchClass); - } - getActivity().sendBroadcast(intent, Manifest.permission.BLUETOOTH_ADMIN); + + mContext.sendBroadcast(intent, Manifest.permission.BLUETOOTH_ADMIN); } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothPermissionActivityTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothPermissionActivityTest.java new file mode 100644 index 00000000000..35b8f1f00a1 --- /dev/null +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothPermissionActivityTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2021 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.bluetooth; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.content.Intent; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(RobolectricTestRunner.class) +public class BluetoothPermissionActivityTest { + + private BluetoothPermissionActivity mActivity; + private Context mContext; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + mActivity = new BluetoothPermissionActivity(); + } + + @Test + public void sendBroadcastWithPermission() { + final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + ReflectionHelpers.setField(mActivity, "mBase", mContext); + + mActivity.sendReplyIntentToReceiver(true, true); + + verify(mContext).sendBroadcast(intentCaptor.capture(), + eq("android.permission.BLUETOOTH_ADMIN")); + } +} diff --git a/tests/robotests/src/com/android/settings/bluetooth/DevicePickerFragmentTest.java b/tests/robotests/src/com/android/settings/bluetooth/DevicePickerFragmentTest.java index feaf6831420..d9e054d86a6 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/DevicePickerFragmentTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/DevicePickerFragmentTest.java @@ -16,28 +16,43 @@ package com.android.settings.bluetooth; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.content.Intent; + +import com.android.settingslib.bluetooth.CachedBluetoothDevice; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; @RunWith(RobolectricTestRunner.class) public class DevicePickerFragmentTest { @Mock private BluetoothProgressCategory mAvailableDevicesCategory; + private DevicePickerFragment mFragment; + private Context mContext; @Before public void setUp() { MockitoAnnotations.initMocks(this); mFragment = new DevicePickerFragment(); - + mContext = spy(RuntimeEnvironment.application); + mFragment.mContext = mContext; mFragment.mAvailableDevicesCategory = mAvailableDevicesCategory; } @@ -49,4 +64,18 @@ public class DevicePickerFragmentTest { verify(mAvailableDevicesCategory).setProgress(true); } + + @Test + public void sendBroadcastWithPermission() { + final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class); + final BluetoothDevice bluetoothDevice = mock(BluetoothDevice.class); + final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + when(cachedDevice.getDevice()).thenReturn(bluetoothDevice); + mFragment.mSelectedDevice = bluetoothDevice; + + mFragment.onDeviceBondStateChanged(cachedDevice, BluetoothDevice.BOND_BONDED); + + verify(mContext).sendBroadcast(intentCaptor.capture(), + eq("android.permission.BLUETOOTH_ADMIN")); + } } From cdf9a1509b0ef1450b2b9b8c349abdbc7902be95 Mon Sep 17 00:00:00 2001 From: Hugh Chen Date: Fri, 12 Mar 2021 10:40:20 +0800 Subject: [PATCH 4/5] RESTRICT AUTOMERGE Fix unable to send file via OPP Bluetooth app will indicate BluetoothOppReceiver to receive device picker intent. But for fix the security issue we removed the setClassName() method in ag/14111132 to avoid attack. It causes BluetoothOppReceiver cannot receive the intent. This CL will compare to calling package name with launch package name. If they are not equal, the setClassName() will not invoke. Bug: 186490534 Bug: 179386960 Bug: 179386068 Test: make RunSettingsRoboTests -j56 Change-Id: Ia51528f2a44ab73edbc86899ca0846d3262fe1f0 (cherry picked from commit bb5be240c0982f2e098978fec187fc735c0e7ad9) (cherry picked from commit c556938807b440c54ce3c4e949460a8ac9e67753) --- .../bluetooth/DevicePickerFragment.java | 22 ++++++++++++++++ .../bluetooth/DevicePickerFragmentTest.java | 26 ++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/bluetooth/DevicePickerFragment.java b/src/com/android/settings/bluetooth/DevicePickerFragment.java index f60183b1c4f..02b5d5c90f0 100644 --- a/src/com/android/settings/bluetooth/DevicePickerFragment.java +++ b/src/com/android/settings/bluetooth/DevicePickerFragment.java @@ -27,12 +27,15 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.UserManager; +import android.util.Log; +import android.text.TextUtils; import android.view.Menu; import android.view.MenuInflater; import androidx.annotation.VisibleForTesting; import com.android.settings.R; +import com.android.settings.password.PasswordUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.core.AbstractPreferenceController; @@ -50,6 +53,12 @@ public final class DevicePickerFragment extends DeviceListPreferenceFragment { BluetoothProgressCategory mAvailableDevicesCategory; @VisibleForTesting Context mContext; + @VisibleForTesting + String mLaunchPackage; + @VisibleForTesting + String mLaunchClass; + @VisibleForTesting + String mCallingAppPackageName; private boolean mNeedAuth; private boolean mScanAllowed; @@ -64,6 +73,8 @@ public final class DevicePickerFragment extends DeviceListPreferenceFragment { mNeedAuth = intent.getBooleanExtra(BluetoothDevicePicker.EXTRA_NEED_AUTH, false); setFilter(intent.getIntExtra(BluetoothDevicePicker.EXTRA_FILTER_TYPE, BluetoothDevicePicker.FILTER_TYPE_ALL)); + mLaunchPackage = intent.getStringExtra(BluetoothDevicePicker.EXTRA_LAUNCH_PACKAGE); + mLaunchClass = intent.getStringExtra(BluetoothDevicePicker.EXTRA_LAUNCH_CLASS); mAvailableDevicesCategory = (BluetoothProgressCategory) findPreference(KEY_BT_DEVICE_LIST); } @@ -83,6 +94,12 @@ public final class DevicePickerFragment extends DeviceListPreferenceFragment { getActivity().setTitle(getString(R.string.device_picker)); UserManager um = (UserManager) getSystemService(Context.USER_SERVICE); mScanAllowed = !um.hasUserRestriction(DISALLOW_CONFIG_BLUETOOTH); + mCallingAppPackageName = PasswordUtils.getCallingAppPackageName( + getActivity().getActivityToken()); + if (!TextUtils.equals(mCallingAppPackageName, mLaunchPackage)) { + Log.w(TAG, "sendDevicePickedIntent() launch package name is not equivalent to" + + " calling package name!"); + } mContext = getContext(); setHasOptionsMenu(true); } @@ -189,6 +206,11 @@ public final class DevicePickerFragment extends DeviceListPreferenceFragment { private void sendDevicePickedIntent(BluetoothDevice device) { Intent intent = new Intent(BluetoothDevicePicker.ACTION_DEVICE_SELECTED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + if (mLaunchPackage != null && mLaunchClass != null) { + if (TextUtils.equals(mCallingAppPackageName, mLaunchPackage)) { + intent.setClassName(mLaunchPackage, mLaunchClass); + } + } mContext.sendBroadcast(intent, Manifest.permission.BLUETOOTH_ADMIN); } diff --git a/tests/robotests/src/com/android/settings/bluetooth/DevicePickerFragmentTest.java b/tests/robotests/src/com/android/settings/bluetooth/DevicePickerFragmentTest.java index d9e054d86a6..a05ec87b71b 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/DevicePickerFragmentTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/DevicePickerFragmentTest.java @@ -16,8 +16,11 @@ package com.android.settings.bluetooth; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -66,16 +69,37 @@ public class DevicePickerFragmentTest { } @Test - public void sendBroadcastWithPermission() { + public void callingPackageIsEqualToLaunchPackage_sendBroadcastToLaunchPackage() { final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class); final BluetoothDevice bluetoothDevice = mock(BluetoothDevice.class); final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); when(cachedDevice.getDevice()).thenReturn(bluetoothDevice); mFragment.mSelectedDevice = bluetoothDevice; + mFragment.mLaunchPackage = "com.android.settings"; + mFragment.mLaunchClass = "com.android.settings.bluetooth.BluetoothPermissionActivity"; + mFragment.mCallingAppPackageName = "com.android.settings"; mFragment.onDeviceBondStateChanged(cachedDevice, BluetoothDevice.BOND_BONDED); verify(mContext).sendBroadcast(intentCaptor.capture(), eq("android.permission.BLUETOOTH_ADMIN")); + assertThat(intentCaptor.getValue().getComponent().getPackageName()) + .isEqualTo(mFragment.mLaunchPackage); + } + + @Test + public void callingPackageIsNotEqualToLaunchPackage_broadcastNotSend() { + final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class); + final BluetoothDevice bluetoothDevice = mock(BluetoothDevice.class); + final ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + when(cachedDevice.getDevice()).thenReturn(bluetoothDevice); + mFragment.mSelectedDevice = bluetoothDevice; + mFragment.mLaunchPackage = "com.fake.settings"; + mFragment.mLaunchClass = "com.android.settings.bluetooth.BluetoothPermissionActivity"; + mFragment.mCallingAppPackageName = "com.android.settings"; + + mFragment.onDeviceBondStateChanged(cachedDevice, BluetoothDevice.BOND_BONDED); + + verify(mContext, never()).sendBroadcast(intentCaptor.capture()); } } From 6eb27a6d0a85598d1d92c94026ae08a1546a2e1a Mon Sep 17 00:00:00 2001 From: Weng Su Date: Thu, 29 Apr 2021 18:08:56 +0800 Subject: [PATCH 5/5] [DO NOT MERGE] Modify Wi-Fi details settings to restricted style - Show restricted text in guest mode - Screenshot: https://screenshot.googleplex.com/6nYcmazMM46TxaB Bug: 177573895 Test: manual test make RunSettingsRoboTests \ ROBOTEST_FILTER=WifiNetworkDetailsFragmentTest Change-Id: I5f857b2079e0f550e4be601d27dd54dac56b2f57 (cherry picked from commit d4cec9b628ba8004eb96adf7fca9091166981c03) --- .../details/WifiNetworkDetailsFragment.java | 45 ++++++++- .../WifiNetworkDetailsFragmentTest.java | 99 +++++++++++++++++++ 2 files changed, 139 insertions(+), 5 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/wifi/details/WifiNetworkDetailsFragmentTest.java diff --git a/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java b/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java index 8af9a7f1905..9aefa7d4a34 100644 --- a/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java +++ b/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java @@ -25,12 +25,15 @@ import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.Handler; import android.os.Looper; +import android.os.UserManager; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import androidx.annotation.VisibleForTesting; + import com.android.settings.R; -import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.dashboard.RestrictedDashboardFragment; import com.android.settings.wifi.WifiConfigUiBase; import com.android.settings.wifi.WifiDialog; import com.android.settingslib.RestrictedLockUtils; @@ -51,15 +54,45 @@ import java.util.List; * future, please develop in * {@link com.android.settings.wifi.details2.WifiNetworkDetailsFragment2}. */ -public class WifiNetworkDetailsFragment extends DashboardFragment implements +public class WifiNetworkDetailsFragment extends RestrictedDashboardFragment implements WifiDialog.WifiDialogListener { private static final String TAG = "WifiNetworkDetailsFrg"; + @VisibleForTesting + boolean mIsUiRestricted; + private AccessPoint mAccessPoint; private WifiDetailPreferenceController mWifiDetailPreferenceController; private List mWifiDialogListeners = new ArrayList<>(); + public WifiNetworkDetailsFragment() { + super(UserManager.DISALLOW_CONFIG_WIFI); + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + setIfOnlyAvailableForAdmins(true); + mIsUiRestricted = isUiRestricted(); + } + + @Override + public void onStart() { + super.onStart(); + if (mIsUiRestricted) { + restrictUi(); + } + } + + @VisibleForTesting + void restrictUi() { + if (!isUiRestrictedByOnlyAdmin()) { + getEmptyTextView().setText(R.string.wifi_empty_list_user_restricted); + } + getPreferenceScreen().removeAll(); + } + @Override public void onAttach(Context context) { mAccessPoint = new AccessPoint(context, getArguments()); @@ -102,9 +135,11 @@ public class WifiNetworkDetailsFragment extends DashboardFragment implements @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - MenuItem item = menu.add(0, Menu.FIRST, 0, R.string.wifi_modify); - item.setIcon(com.android.internal.R.drawable.ic_mode_edit); - item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + if (!mIsUiRestricted) { + MenuItem item = menu.add(0, Menu.FIRST, 0, R.string.wifi_modify); + item.setIcon(com.android.internal.R.drawable.ic_mode_edit); + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + } super.onCreateOptionsMenu(menu, inflater); } diff --git a/tests/robotests/src/com/android/settings/wifi/details/WifiNetworkDetailsFragmentTest.java b/tests/robotests/src/com/android/settings/wifi/details/WifiNetworkDetailsFragmentTest.java new file mode 100644 index 00000000000..f4693cf5658 --- /dev/null +++ b/tests/robotests/src/com/android/settings/wifi/details/WifiNetworkDetailsFragmentTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2021 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.details; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.view.Menu; +import android.view.MenuInflater; +import android.widget.TextView; + +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class WifiNetworkDetailsFragmentTest { + + @Mock + Menu mMenu; + private WifiNetworkDetailsFragment mFragment; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mFragment = new WifiNetworkDetailsFragment(); + } + + @Test + public void onCreateOptionsMenu_uiRestricted_shouldNotAddEditMenu() { + mFragment.mIsUiRestricted = true; + + mFragment.onCreateOptionsMenu(mMenu, mock(MenuInflater.class)); + + verify(mMenu, never()).add(anyInt(), anyInt(), anyInt(), eq(R.string.wifi_modify)); + } + + @Test + public void restrictUi_shouldShowRestrictedText() { + final WifiNetworkDetailsFragmentTest.FakeFragment + fragment = spy(new WifiNetworkDetailsFragmentTest.FakeFragment()); + final PreferenceScreen screen = mock(PreferenceScreen.class); + final TextView restrictedText = mock(TextView.class); + doReturn(screen).when(fragment).getPreferenceScreen(); + doReturn(false).when(fragment).isUiRestrictedByOnlyAdmin(); + doReturn(restrictedText).when(fragment).getEmptyTextView(); + + fragment.restrictUi(); + + verify(restrictedText).setText(anyInt()); + } + + @Test + public void restrictUi_shouldRemoveAllPreferences() { + final WifiNetworkDetailsFragmentTest.FakeFragment + fragment = spy(new WifiNetworkDetailsFragmentTest.FakeFragment()); + final PreferenceScreen screen = mock(PreferenceScreen.class); + doReturn(screen).when(fragment).getPreferenceScreen(); + doReturn(true).when(fragment).isUiRestrictedByOnlyAdmin(); + + fragment.restrictUi(); + + verify(screen).removeAll(); + } + + // Fake WifiNetworkDetailsFragment to override the protected method as public. + public class FakeFragment extends WifiNetworkDetailsFragment { + @Override + public boolean isUiRestrictedByOnlyAdmin() { + return super.isUiRestrictedByOnlyAdmin(); + } + } +} \ No newline at end of file