Merge "Add code for interfacing with PM"
This commit is contained in:
committed by
Android (Google) Code Review
commit
c086760104
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user