diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-14 17:33:49 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-14 17:33:49 +0000 |
commit | 8f51779eb0817006b552ba47fa873a0b4f21ea1f (patch) | |
tree | a76e96c3fc18f15160f9478f55d9743a98ee7ffb | |
parent | 9fac3e3565a8cae26b40ca7f17e2af76fb3c9b05 (diff) | |
parent | b8a46b8ac930f4aa33df0689b341789515bcf5ce (diff) | |
download | Settings-8f51779eb0817006b552ba47fa873a0b4f21ea1f.tar.gz |
Merge cherrypicks of ['googleplex-android-review.googlesource.com/23563580', 'googleplex-android-review.googlesource.com/23712724', 'googleplex-android-review.googlesource.com/23545222'] into security-aosp-sc-release.android-security-12.0.0_r51
Change-Id: Iedb42b254e46d31832f0e846df8165173a441a48
7 files changed, 252 insertions, 73 deletions
diff --git a/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminAdd.java b/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminAdd.java index 7b2f4ba15af..2a4250a4fcb 100644 --- a/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminAdd.java +++ b/src/com/android/settings/applications/specialaccess/deviceadmin/DeviceAdminAdd.java @@ -52,6 +52,7 @@ import android.text.TextUtils.TruncateAt; import android.util.EventLog; import android.util.Log; import android.view.Display; +import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -142,12 +143,12 @@ public class DeviceAdminAdd extends CollapsingToolbarBaseActivity { mHandler = new Handler(getMainLooper()); - mDPM = (DevicePolicyManager)getSystemService(Context.DEVICE_POLICY_SERVICE); - mAppOps = (AppOpsManager)getSystemService(Context.APP_OPS_SERVICE); - mLayoutInflaternflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mDPM = getSystemService(DevicePolicyManager.class); + mAppOps = getSystemService(AppOpsManager.class); + mLayoutInflaternflater = getSystemService(LayoutInflater.class); PackageManager packageManager = getPackageManager(); - if ((getIntent().getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { + if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { Log.w(TAG, "Cannot start ADD_DEVICE_ADMIN as a new task"); finish(); return; @@ -157,7 +158,7 @@ public class DeviceAdminAdd extends CollapsingToolbarBaseActivity { EXTRA_CALLED_FROM_SUPPORT_DIALOG, false); String action = getIntent().getAction(); - ComponentName who = (ComponentName)getIntent().getParcelableExtra( + ComponentName who = (ComponentName) getIntent().getParcelableExtra( DevicePolicyManager.EXTRA_DEVICE_ADMIN); if (who == null) { String packageName = getIntent().getStringExtra(EXTRA_DEVICE_ADMIN_PACKAGE_NAME); @@ -215,7 +216,7 @@ public class DeviceAdminAdd extends CollapsingToolbarBaseActivity { PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS); int count = avail == null ? 0 : avail.size(); boolean found = false; - for (int i=0; i<count; i++) { + for (int i = 0; i < count; i++) { ResolveInfo ri = avail.get(i); if (ai.packageName.equals(ri.activityInfo.packageName) && ai.name.equals(ri.activityInfo.name)) { @@ -345,12 +346,12 @@ public class DeviceAdminAdd extends CollapsingToolbarBaseActivity { } setContentView(R.layout.device_admin_add); - mAdminIcon = (ImageView)findViewById(R.id.admin_icon); - mAdminName = (TextView)findViewById(R.id.admin_name); - mAdminDescription = (TextView)findViewById(R.id.admin_description); + mAdminIcon = (ImageView) findViewById(R.id.admin_icon); + mAdminName = (TextView) findViewById(R.id.admin_name); + mAdminDescription = (TextView) findViewById(R.id.admin_description); mProfileOwnerWarning = (TextView) findViewById(R.id.profile_owner_warning); - mAddMsg = (TextView)findViewById(R.id.add_msg); + mAddMsg = (TextView) findViewById(R.id.add_msg); mAddMsgExpander = (ImageView) findViewById(R.id.add_msg_expander); final View.OnClickListener onClickListener = new View.OnClickListener() { @Override @@ -371,7 +372,7 @@ public class DeviceAdminAdd extends CollapsingToolbarBaseActivity { boolean hideMsgExpander = mAddMsg.getLineCount() <= maxLines; mAddMsgExpander.setVisibility(hideMsgExpander ? View.GONE : View.VISIBLE); if (hideMsgExpander) { - ((View)mAddMsgExpander.getParent()).invalidate(); + ((View) mAddMsgExpander.getParent()).invalidate(); } mAddMsg.getViewTreeObserver().removeOnGlobalLayoutListener(this); } @@ -389,7 +390,7 @@ public class DeviceAdminAdd extends CollapsingToolbarBaseActivity { mCancelButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { EventLog.writeEvent(EventLogTags.EXP_DET_DEVICE_ADMIN_DECLINED_BY_USER, - mDeviceAdmin.getActivityInfo().applicationInfo.uid); + mDeviceAdmin.getActivityInfo().applicationInfo.uid); finish(); } }); @@ -409,58 +410,64 @@ public class DeviceAdminAdd extends CollapsingToolbarBaseActivity { final View restrictedAction = findViewById(R.id.restricted_action); restrictedAction.setFilterTouchesWhenObscured(true); - restrictedAction.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - if (!mActionButton.isEnabled()) { - showPolicyTransparencyDialogIfRequired(); - return; - } - if (mAdding) { - addAndFinish(); - } else if (isManagedProfile(mDeviceAdmin) - && mDeviceAdmin.getComponent().equals(mDPM.getProfileOwner())) { - final int userId = UserHandle.myUserId(); - UserDialogs.createRemoveDialog(DeviceAdminAdd.this, userId, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - UserManager um = UserManager.get(DeviceAdminAdd.this); - um.removeUser(userId); - finish(); - } + + final View.OnClickListener restrictedActionClickListener = v -> { + if (!mActionButton.isEnabled()) { + showPolicyTransparencyDialogIfRequired(); + return; + } + if (mAdding) { + addAndFinish(); + } else if (isManagedProfile(mDeviceAdmin) + && mDeviceAdmin.getComponent().equals(mDPM.getProfileOwner())) { + final int userId = UserHandle.myUserId(); + UserDialogs.createRemoveDialog(DeviceAdminAdd.this, userId, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + UserManager um = UserManager.get(DeviceAdminAdd.this); + um.removeUser(userId); + finish(); } - ).show(); - } else if (mUninstalling) { - mDPM.uninstallPackageWithActiveAdmins(mDeviceAdmin.getPackageName()); - finish(); - } else if (!mWaitingForRemoveMsg) { - try { - // Don't allow the admin to put a dialog up in front - // of us while we interact with the user. - ActivityManager.getService().stopAppSwitches(); - } catch (RemoteException e) { - } - mWaitingForRemoveMsg = true; - mDPM.getRemoveWarning(mDeviceAdmin.getComponent(), - new RemoteCallback(new RemoteCallback.OnResultListener() { - @Override - public void onResult(Bundle result) { - CharSequence msg = result != null - ? result.getCharSequence( - DeviceAdminReceiver.EXTRA_DISABLE_WARNING) - : null; - continueRemoveAction(msg); - } - }, mHandler)); - // Don't want to wait too long. - getWindow().getDecorView().getHandler().postDelayed(new Runnable() { - @Override public void run() { - continueRemoveAction(null); } - }, 2*1000); + ).show(); + } else if (mUninstalling) { + mDPM.uninstallPackageWithActiveAdmins(mDeviceAdmin.getPackageName()); + finish(); + } else if (!mWaitingForRemoveMsg) { + try { + // Don't allow the admin to put a dialog up in front + // of us while we interact with the user. + ActivityManager.getService().stopAppSwitches(); + } catch (RemoteException e) { } + mWaitingForRemoveMsg = true; + mDPM.getRemoveWarning(mDeviceAdmin.getComponent(), + new RemoteCallback(new RemoteCallback.OnResultListener() { + @Override + public void onResult(Bundle result) { + CharSequence msg = result != null + ? result.getCharSequence( + DeviceAdminReceiver.EXTRA_DISABLE_WARNING) + : null; + continueRemoveAction(msg); + } + }, mHandler)); + // Don't want to wait too long. + getWindow().getDecorView().getHandler().postDelayed( + () -> continueRemoveAction(null), 2 * 1000); + } + }; + restrictedAction.setOnKeyListener((view, keyCode, keyEvent) -> { + if ((keyEvent.getFlags() & KeyEvent.FLAG_FROM_SYSTEM) == 0) { + Log.e(TAG, "Can not activate device-admin with KeyEvent from non-system app."); + // Consume event to suppress click. + return true; } + // Fallback to view click handler. + return false; }); + restrictedAction.setOnClickListener(restrictedActionClickListener); } /** diff --git a/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java b/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java index a43b9fd9145..9235494f359 100644 --- a/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java +++ b/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceController.java @@ -81,6 +81,9 @@ public class ApprovalPreferenceController extends BasePreferenceController { final SwitchPreference preference = (SwitchPreference) pref; final CharSequence label = mPkgInfo.applicationInfo.loadLabel(mPm); preference.setChecked(isServiceEnabled(mCn)); + final boolean isAllowedCn = mCn.flattenToShortString().length() + <= NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH; + preference.setEnabled(preference.isChecked() || isAllowedCn); preference.setOnPreferenceChangeListener((p, newValue) -> { final boolean access = (Boolean) newValue; if (!access) { diff --git a/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java b/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java index dfe6df2a5ca..a6b565ae6ba 100644 --- a/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java +++ b/src/com/android/settings/notification/NotificationAccessConfirmationActivity.java @@ -67,7 +67,9 @@ public class NotificationAccessConfirmationActivity extends Activity mUserId = getIntent().getIntExtra(EXTRA_USER_ID, UserHandle.USER_NULL); CharSequence mAppLabel; - if (mComponentName == null || mComponentName.getPackageName() == null) { + if (mComponentName == null || mComponentName.getPackageName() == null + || mComponentName.flattenToString().length() + > NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) { finish(); return; } diff --git a/src/com/android/settings/notification/NotificationAccessSettings.java b/src/com/android/settings/notification/NotificationAccessSettings.java index d94498e68f7..00543a2f3c0 100644 --- a/src/com/android/settings/notification/NotificationAccessSettings.java +++ b/src/com/android/settings/notification/NotificationAccessSettings.java @@ -40,6 +40,7 @@ import android.widget.Toast; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; +import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.applications.AppInfoBase; @@ -60,9 +61,8 @@ import java.util.List; @SearchIndexable public class NotificationAccessSettings extends EmptyTextSettings { private static final String TAG = "NotifAccessSettings"; - private static final String ALLOWED_KEY = "allowed"; - private static final String NOT_ALLOWED_KEY = "not_allowed"; - private static final int MAX_CN_LENGTH = 500; + static final String ALLOWED_KEY = "allowed"; + static final String NOT_ALLOWED_KEY = "not_allowed"; private static final ManagedServiceSettings.Config CONFIG = new ManagedServiceSettings.Config.Builder() @@ -77,9 +77,9 @@ public class NotificationAccessSettings extends EmptyTextSettings { .setEmptyText(R.string.no_notification_listeners) .build(); - private NotificationManager mNm; + @VisibleForTesting NotificationManager mNm; protected Context mContext; - private PackageManager mPm; + @VisibleForTesting PackageManager mPm; private DevicePolicyManager mDpm; private ServiceListing mServiceListing; private IconDrawableFactory mIconDrawableFactory; @@ -99,12 +99,6 @@ public class NotificationAccessSettings extends EmptyTextSettings { .setNoun(CONFIG.noun) .setSetting(CONFIG.setting) .setTag(CONFIG.tag) - .setValidator(info -> { - if (info.getComponentName().flattenToString().length() > MAX_CN_LENGTH) { - return false; - } - return true; - }) .build(); mServiceListing.addCallback(this::updateList); @@ -135,7 +129,8 @@ public class NotificationAccessSettings extends EmptyTextSettings { mServiceListing.setListening(false); } - private void updateList(List<ServiceInfo> services) { + @VisibleForTesting + void updateList(List<ServiceInfo> services) { final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); final int managedProfileId = Utils.getManagedProfileId(um, UserHandle.myUserId()); @@ -148,6 +143,12 @@ public class NotificationAccessSettings extends EmptyTextSettings { services.sort(new PackageItemInfo.DisplayNameComparator(mPm)); for (ServiceInfo service : services) { final ComponentName cn = new ComponentName(service.packageName, service.name); + boolean isAllowed = mNm.isNotificationListenerAccessGranted(cn); + if (!isAllowed && cn.flattenToString().length() + > NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) { + continue; + } + CharSequence title = null; try { title = mPm.getApplicationInfoAsUser( @@ -192,7 +193,7 @@ public class NotificationAccessSettings extends EmptyTextSettings { return true; }); pref.setKey(cn.flattenToString()); - if (mNm.isNotificationListenerAccessGranted(cn)) { + if (isAllowed) { allowedCategory.addPreference(pref); } else { notAllowedCategory.addPreference(pref); diff --git a/src/com/android/settings/notification/NotificationBackend.java b/src/com/android/settings/notification/NotificationBackend.java index e448dda20aa..d95b48f3932 100644 --- a/src/com/android/settings/notification/NotificationBackend.java +++ b/src/com/android/settings/notification/NotificationBackend.java @@ -143,6 +143,9 @@ public class NotificationBackend { static public CharSequence getDeviceList(ICompanionDeviceManager cdm, LocalBluetoothManager lbm, String pkg, int userId) { + if (cdm == null) { + return ""; + } boolean multiple = false; StringBuilder sb = new StringBuilder(); diff --git a/tests/robotests/src/com/android/settings/notification/NotificationAccessSettingsTest.java b/tests/robotests/src/com/android/settings/notification/NotificationAccessSettingsTest.java new file mode 100644 index 00000000000..e644c2975b7 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/NotificationAccessSettingsTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2023 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.notification; + +import static com.android.settings.notification.NotificationAccessSettings.ALLOWED_KEY; +import static com.android.settings.notification.NotificationAccessSettings.NOT_ALLOWED_KEY; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.app.NotificationManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; + +import androidx.fragment.app.FragmentActivity; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceScreen; + +import com.android.settings.testutils.shadow.ShadowBluetoothUtils; +import com.android.settingslib.bluetooth.LocalBluetoothManager; + +import com.google.common.base.Strings; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.stubbing.Answer; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +import java.util.ArrayList; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowBluetoothUtils.class}) +public class NotificationAccessSettingsTest { + + private Context mContext; + private NotificationAccessSettings mAccessSettings; + @Mock + private NotificationManager mNotificationManager; + @Mock + private PackageManager mPackageManager; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mContext = RuntimeEnvironment.application; + ShadowApplication shadowApp = ShadowApplication.getInstance(); + shadowApp.setSystemService(Context.NOTIFICATION_SERVICE, mNotificationManager); + + mAccessSettings = new NotificationAccessSettings(); + FragmentActivity activity = Robolectric.buildActivity(FragmentActivity.class).setup().get(); + activity.getSupportFragmentManager().beginTransaction().add(mAccessSettings, null).commit(); + + when(mPackageManager.getApplicationInfoAsUser(any(), anyInt(), anyInt())).then( + (Answer<ApplicationInfo>) invocation -> { + ApplicationInfo appInfo = mock(ApplicationInfo.class); + when(appInfo.loadLabel(any())).thenReturn(invocation.getArgument(0)); + return appInfo; + }); + + mAccessSettings.mNm = mNotificationManager; + mAccessSettings.mPm = mPackageManager; + ShadowBluetoothUtils.sLocalBluetoothManager = mock(LocalBluetoothManager.class); + } + + @Test + public void updateList_enabledLongName_shown() { + ComponentName longCn = new ComponentName("test.pkg1", + Strings.repeat("Blah", 200) + "Service"); + ComponentName shortCn = new ComponentName("test.pkg2", "ReasonableService"); + ArrayList<ServiceInfo> services = new ArrayList<>(); + services.add(newServiceInfo(longCn.getPackageName(), longCn.getClassName(), 1)); + services.add(newServiceInfo(shortCn.getPackageName(), shortCn.getClassName(), 2)); + when(mNotificationManager.isNotificationListenerAccessGranted(any())).thenReturn(true); + + mAccessSettings.updateList(services); + + PreferenceScreen screen = mAccessSettings.getPreferenceScreen(); + PreferenceCategory allowed = checkNotNull(screen.findPreference(ALLOWED_KEY)); + PreferenceCategory notAllowed = checkNotNull(screen.findPreference(NOT_ALLOWED_KEY)); + assertThat(allowed.getPreferenceCount()).isEqualTo(2); + assertThat(allowed.getPreference(0).getKey()).isEqualTo(longCn.flattenToString()); + assertThat(allowed.getPreference(1).getKey()).isEqualTo(shortCn.flattenToString()); + assertThat(notAllowed.getPreferenceCount()).isEqualTo(0); + } + + @Test + public void updateList_disabledLongName_notShown() { + ComponentName longCn = new ComponentName("test.pkg1", + Strings.repeat("Blah", 200) + "Service"); + ComponentName shortCn = new ComponentName("test.pkg2", "ReasonableService"); + ArrayList<ServiceInfo> services = new ArrayList<>(); + services.add(newServiceInfo(longCn.getPackageName(), longCn.getClassName(), 1)); + services.add(newServiceInfo(shortCn.getPackageName(), shortCn.getClassName(), 2)); + when(mNotificationManager.isNotificationListenerAccessGranted(any())).thenReturn(false); + + mAccessSettings.updateList(services); + + PreferenceScreen screen = mAccessSettings.getPreferenceScreen(); + PreferenceCategory allowed = checkNotNull(screen.findPreference(ALLOWED_KEY)); + PreferenceCategory notAllowed = checkNotNull(screen.findPreference(NOT_ALLOWED_KEY)); + assertThat(allowed.getPreferenceCount()).isEqualTo(0); + assertThat(notAllowed.getPreferenceCount()).isEqualTo(1); + assertThat(notAllowed.getPreference(0).getKey()).isEqualTo(shortCn.flattenToString()); + } + + private static ServiceInfo newServiceInfo(String packageName, String serviceName, int uid) { + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.packageName = packageName; + serviceInfo.name = serviceName; + serviceInfo.applicationInfo = new ApplicationInfo(); + serviceInfo.applicationInfo.uid = uid; + return serviceInfo; + } +} diff --git a/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java index 064f8134c67..5316adc1c3a 100644 --- a/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java @@ -78,6 +78,25 @@ public class ApprovalPreferenceControllerTest { } @Test + public void updateState_enabled() { + SwitchPreference pref = new SwitchPreference(mContext); + mController.updateState(pref); + assertThat(pref.isEnabled()).isTrue(); + } + + @Test + public void updateState_invalidCn_disabled() { + ComponentName longCn = new ComponentName("com.example.package", + com.google.common.base.Strings.repeat("Blah", 150)); + mController.setCn(longCn); + SwitchPreference pref = new SwitchPreference(mContext); + + mController.updateState(pref); + + assertThat(pref.isEnabled()).isFalse(); + } + + @Test public void updateState_checked() { when(mNm.isNotificationListenerAccessGranted(mCn)).thenReturn(true); SwitchPreference pref = new SwitchPreference(mContext); |