Add code for interfacing with PM

Allowing NMS to query app notification permission state

Test: PermissionHelperTest
Bug: 194833441
Change-Id: I8d9f7c7e72855abfa286c60ccf1ed4f5fe4f4128
This commit is contained in:
Julia Reynolds
2021-10-10 20:19:52 -04:00
parent 7c7bb9ab40
commit f9b28756b8
2 changed files with 405 additions and 0 deletions

View File

@@ -0,0 +1,160 @@
/*
* 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.server.notification;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.permission.PermissionManager.PERMISSION_GRANTED;
import android.Manifest;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.ParceledListSlice;
import android.os.RemoteException;
import android.permission.IPermissionManager;
import android.util.Slog;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* NotificationManagerService helper for querying/setting the app-level notification permission
*/
public final class PermissionHelper {
private static final String TAG = "PermissionHelper";
private static String NOTIFICATION_PERMISSION = Manifest.permission.POST_NOTIFICATIONS;
private final PermissionManagerServiceInternal mPmi;
private final IPackageManager mPackageManager;
private final IPermissionManager mPermManager;
// TODO (b/194833441): Remove when the migration is enabled
private final boolean mMigrationEnabled;
public PermissionHelper(PermissionManagerServiceInternal pmi, IPackageManager packageManager,
IPermissionManager permManager, boolean migrationEnabled) {
mPmi = pmi;
mPackageManager = packageManager;
mPermManager = permManager;
mMigrationEnabled = migrationEnabled;
}
/**
* Returns whether the given uid holds the notification permission. Must not be called
* with a lock held.
*/
public boolean hasPermission(int uid) {
assertFlag();
return mPmi.checkUidPermission(uid, NOTIFICATION_PERMISSION) == PERMISSION_GRANTED;
}
/**
* Returns all of the apps that have requested the notification permission in a given user.
* Must not be called with a lock held. Format: uid, packageName
*/
public Map<Integer, String> getAppsRequestingPermission(int userId) {
assertFlag();
Map<Integer, String> requested = new HashMap<>();
List<PackageInfo> pkgs = getInstalledPackages(userId);
for (PackageInfo pi : pkgs) {
// when data was stored in PreferencesHelper, we only had data for apps that
// had ever registered an intent to send a notification. To match that behavior,
// filter the app list to apps that have requested the notification permission.
if (pi.requestedPermissions == null) {
continue;
}
for (String perm : pi.requestedPermissions) {
if (NOTIFICATION_PERMISSION.equals(perm)) {
requested.put(pi.applicationInfo.uid, pi.packageName);
break;
}
}
}
return requested;
}
private List<PackageInfo> getInstalledPackages(int userId) {
ParceledListSlice<PackageInfo> parceledList = null;
try {
parceledList = mPackageManager.getInstalledPackages(GET_PERMISSIONS, userId);
} catch (RemoteException e) {
Slog.d(TAG, "Could not reach system server", e);
}
if (parceledList == null) {
return Collections.emptyList();
}
return parceledList.getList();
}
/**
* Returns a list of apps that hold the notification permission. Must not be called
* with a lock held. Format: uid, packageName.
*/
public Map<Integer, String> getAppsGrantedPermission(int userId) {
assertFlag();
Map<Integer, String> granted = new HashMap<>();
ParceledListSlice<PackageInfo> parceledList = null;
try {
parceledList = mPackageManager.getPackagesHoldingPermissions(
new String[] {NOTIFICATION_PERMISSION}, 0, userId);
} catch (RemoteException e) {
Slog.e(TAG, "Could not reach system server", e);
}
if (parceledList == null) {
return granted;
}
for (PackageInfo pi : parceledList.getList()) {
granted.put(pi.applicationInfo.uid, pi.packageName);
}
return granted;
}
/**
* Grants or revokes the notification permission for a given package/user. UserSet should
* only be true if this method is being called to migrate existing user choice, because it
* can prevent the user from seeing the in app permission dialog. Must not be called
* with a lock held.
*/
public void setNotificationPermission(String packageName, int userId, boolean grant,
boolean userSet) {
assertFlag();
try {
if (grant) {
mPermManager.grantRuntimePermission(packageName, NOTIFICATION_PERMISSION, userId);
} else {
mPermManager.revokeRuntimePermission(packageName, NOTIFICATION_PERMISSION, userId,
TAG);
}
if (userSet) {
mPermManager.updatePermissionFlags(packageName, NOTIFICATION_PERMISSION,
FLAG_PERMISSION_USER_SET, FLAG_PERMISSION_USER_SET, true, userId);
}
} catch (RemoteException e) {
Slog.e(TAG, "Could not reach system server", e);
}
}
private void assertFlag() {
if (!mMigrationEnabled) {
throw new IllegalStateException("Method called without checking flag value");
}
}
}

View File

@@ -0,0 +1,245 @@
/*
* 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.server.notification;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.permission.PermissionManager.PERMISSION_GRANTED;
import static android.permission.PermissionManager.PERMISSION_SOFT_DENIED;
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.fail;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.Manifest;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.ParceledListSlice;
import android.permission.IPermissionManager;
import android.test.suitebuilder.annotation.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.UiServiceTestCase;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class PermissionHelperTest extends UiServiceTestCase {
@Mock
private PermissionManagerServiceInternal mPmi;
@Mock
private IPackageManager mPackageManager;
@Mock
private IPermissionManager mPermManager;
private PermissionHelper mPermissionHelper;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mPermissionHelper = new PermissionHelper(mPmi, mPackageManager, mPermManager, true);
}
// TODO (b/194833441): Remove when the migration is enabled
@Test
public void testMethodsThrowIfMigrationDisabled() throws IllegalAccessException,
InvocationTargetException {
PermissionHelper permHelper =
new PermissionHelper(mPmi, mPackageManager, mPermManager, false);
Method[] allMethods = PermissionHelper.class.getDeclaredMethods();
for (Method method : allMethods) {
if (Modifier.isPublic(method.getModifiers())) {
Parameter[] params = method.getParameters();
List<Object> args = Lists.newArrayListWithCapacity(params.length);
for (int i = 0; i < params.length; i++) {
Type type = params[i].getParameterizedType();
if (type.getTypeName().equals("java.lang.String")) {
args.add("");
} else if (type.getTypeName().equals("boolean")){
args.add(false);
} else if (type.getTypeName().equals("int")) {
args.add(1);
}
}
try {
method.invoke(permHelper, args.toArray());
fail("Method should have thrown because migration flag is disabled");
} catch (InvocationTargetException e) {
if (!(e.getTargetException() instanceof IllegalStateException)) {
throw e;
}
}
}
}
}
@Test
public void testHasPermission() throws Exception {
when(mPmi.checkUidPermission(anyInt(), eq(Manifest.permission.POST_NOTIFICATIONS)))
.thenReturn(PERMISSION_GRANTED);
assertThat(mPermissionHelper.hasPermission(1)).isTrue();
when(mPmi.checkUidPermission(anyInt(), eq(Manifest.permission.POST_NOTIFICATIONS)))
.thenReturn(PERMISSION_SOFT_DENIED);
assertThat(mPermissionHelper.hasPermission(1)).isFalse();
}
@Test
public void testGetAppsRequestingPermission() throws Exception {
// App that does not request permission
PackageInfo notThis = new PackageInfo();
notThis.packageName = "wrong.permission";
notThis.requestedPermissions = new String[] {"something else"};
// App that does not request any permissions (null check
PackageInfo none = new PackageInfo();
none.packageName = "no.permissions";
// 2 apps that request the permission
PackageInfo first = new PackageInfo();
first.packageName = "first";
first.requestedPermissions =
new String[] {"something else", Manifest.permission.POST_NOTIFICATIONS};
ApplicationInfo aiFirst = new ApplicationInfo();
aiFirst.uid = 1;
first.applicationInfo = aiFirst;
PackageInfo second = new PackageInfo();
second.packageName = "second";
second.requestedPermissions = new String[] {Manifest.permission.POST_NOTIFICATIONS};
ApplicationInfo aiSecond = new ApplicationInfo();
aiSecond.uid = 2;
second.applicationInfo = aiSecond;
Map<Integer, String> expected = ImmutableMap.of(1, "first", 2, "second");
ParceledListSlice<PackageInfo> infos = new ParceledListSlice<>(
ImmutableList.of(notThis, none, first, second));
when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS), anyInt())).thenReturn(infos);
Map<Integer, String> actual = mPermissionHelper.getAppsRequestingPermission(0);
assertThat(actual).containsExactlyEntriesIn(expected);
}
@Test
public void testGetAppsGrantedPermission_noApps() throws Exception {
int userId = 1;
ParceledListSlice<PackageInfo> infos = ParceledListSlice.emptyList();
when(mPackageManager.getPackagesHoldingPermissions(
eq(new String[] {Manifest.permission.POST_NOTIFICATIONS}), anyInt(), eq(userId)))
.thenReturn(infos);
assertThat(mPermissionHelper.getAppsGrantedPermission(userId)).isNotNull();
}
@Test
public void testGetAppsGrantedPermission() throws Exception {
int userId = 1;
PackageInfo first = new PackageInfo();
first.packageName = "first";
first.requestedPermissions =
new String[] {"something else", Manifest.permission.POST_NOTIFICATIONS};
ApplicationInfo aiFirst = new ApplicationInfo();
aiFirst.uid = 1;
first.applicationInfo = aiFirst;
PackageInfo second = new PackageInfo();
second.packageName = "second";
second.requestedPermissions = new String[] {Manifest.permission.POST_NOTIFICATIONS};
ApplicationInfo aiSecond = new ApplicationInfo();
aiSecond.uid = 2;
second.applicationInfo = aiSecond;
ParceledListSlice<PackageInfo> infos = new ParceledListSlice<>(
ImmutableList.of(first, second));
when(mPackageManager.getPackagesHoldingPermissions(
eq(new String[] {Manifest.permission.POST_NOTIFICATIONS}), anyInt(), eq(userId)))
.thenReturn(infos);
Map<Integer, String> expected = ImmutableMap.of(1, "first", 2, "second");
assertThat(mPermissionHelper.getAppsGrantedPermission(userId))
.containsExactlyEntriesIn(expected);
}
@Test
public void testSetNotificationPermission_grantUserSet() throws Exception {
mPermissionHelper.setNotificationPermission("pkg", 10, true, true);
verify(mPermManager).grantRuntimePermission(
"pkg", Manifest.permission.POST_NOTIFICATIONS, 10);
verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
FLAG_PERMISSION_USER_SET, FLAG_PERMISSION_USER_SET, true, 10);
}
@Test
public void testSetNotificationPermission_revokeUserSet() throws Exception {
mPermissionHelper.setNotificationPermission("pkg", 10, false, true);
verify(mPermManager).revokeRuntimePermission(
eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS), eq(10), anyString());
verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
FLAG_PERMISSION_USER_SET, FLAG_PERMISSION_USER_SET, true, 10);
}
@Test
public void testSetNotificationPermission_grantNotUserSet() throws Exception {
mPermissionHelper.setNotificationPermission("pkg", 10, true, false);
verify(mPermManager).grantRuntimePermission(
"pkg", Manifest.permission.POST_NOTIFICATIONS, 10);
verify(mPermManager, never()).updatePermissionFlags(
anyString(), anyString(), anyInt(), anyInt(), anyBoolean(), anyInt());
}
@Test
public void testSetNotificationPermission_revokeNotUserSet() throws Exception {
mPermissionHelper.setNotificationPermission("pkg", 10, false, false);
verify(mPermManager).revokeRuntimePermission(
eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS), eq(10), anyString());
verify(mPermManager, never()).updatePermissionFlags(
anyString(), anyString(), anyInt(), anyInt(), anyBoolean(), anyInt());
}
}