method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups();
method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
method public android.app.NotificationManager.Policy getNotificationPolicy();
+ method public boolean isNotificationListenerAccessGranted(android.content.ComponentName);
method public boolean isNotificationPolicyAccessGranted();
method public void notify(int, android.app.Notification);
method public void notify(java.lang.String, int, android.app.Notification);
method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups();
method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
method public android.app.NotificationManager.Policy getNotificationPolicy();
+ method public boolean isNotificationListenerAccessGranted(android.content.ComponentName);
method public boolean isNotificationPolicyAccessGranted();
method public void notify(int, android.app.Notification);
method public void notify(java.lang.String, int, android.app.Notification);
method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups();
method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
method public android.app.NotificationManager.Policy getNotificationPolicy();
+ method public boolean isNotificationListenerAccessGranted(android.content.ComponentName);
method public boolean isNotificationPolicyAccessGranted();
method public void notify(int, android.app.Notification);
method public void notify(java.lang.String, int, android.app.Notification);
field public static final java.lang.String DISABLED_PRINT_SERVICES = "disabled_print_services";
field public static final java.lang.String ENABLED_ACCESSIBILITY_SERVICES = "enabled_accessibility_services";
field public static final java.lang.String ENABLED_INPUT_METHODS = "enabled_input_methods";
- field public static final java.lang.String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES = "enabled_notification_policy_access_packages";
+ field public static final deprecated java.lang.String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES = "enabled_notification_policy_access_packages";
field public static final deprecated java.lang.String HTTP_PROXY = "http_proxy";
field public static final java.lang.String INPUT_METHOD_SELECTOR_VISIBILITY = "input_method_selector_visibility";
field public static final deprecated java.lang.String INSTALL_NON_MARKET_APPS = "install_non_market_apps";
boolean matchesCallFilter(in Bundle extras);
boolean isSystemConditionProviderEnabled(String path);
+ boolean isNotificationListenerAccessGranted(in ComponentName listener);
+ boolean isNotificationListenerAccessGrantedForUser(in ComponentName listener, int userId);
+ boolean isNotificationAssistantAccessGranted(in ComponentName assistant);
+ void setNotificationListenerAccessGranted(in ComponentName listener, boolean enabled);
+ void setNotificationAssistantAccessGranted(in ComponentName assistant, boolean enabled);
+ void setNotificationListenerAccessGrantedForUser(in ComponentName listener, int userId, boolean enabled);
+ void setNotificationAssistantAccessGrantedForUser(in ComponentName assistant, int userId, boolean enabled);
+ List<String> getEnabledNotificationListenerPackages();
+ List<ComponentName> getEnabledNotificationListeners(int userId);
+
int getZenMode();
ZenModeConfig getZenModeConfig();
oneway void setZenMode(int mode, in Uri conditionId, String reason);
boolean isNotificationPolicyAccessGranted(String pkg);
NotificationManager.Policy getNotificationPolicy(String pkg);
void setNotificationPolicy(String pkg, in NotificationManager.Policy policy);
- String[] getPackagesRequestingNotificationPolicyAccess();
boolean isNotificationPolicyAccessGrantedForPackage(String pkg);
void setNotificationPolicyAccessGranted(String pkg, boolean granted);
AutomaticZenRule getAutomaticZenRule(String id);
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.app.Notification.Builder;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.ParceledListSlice;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.StrictMode;
import android.os.UserHandle;
import android.provider.Settings.Global;
-import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.StatusBarNotification;
import android.service.notification.ZenModeConfig;
-import android.util.ArraySet;
import android.util.Log;
import java.lang.annotation.Retention;
}
/**
- * Checks the ability to read/modify notification policy for the calling package.
+ * Checks the ability to read/modify notification do not disturb policy for the calling package.
*
* <p>
* Returns true if the calling package can read/modify notification policy.
*
* <p>
- * Request policy access by sending the user to the activity that matches the system intent
- * action {@link android.provider.Settings#ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS}.
+ * Apps can request policy access by sending the user to the activity that matches the system
+ * intent action {@link android.provider.Settings#ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS}.
*
* <p>
* Use {@link #ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED} to listen for
}
}
+ /**
+ * Checks whether the user has approved a given
+ * {@link android.service.notification.NotificationListenerService}.
+ *
+ * <p>
+ * The listener service must belong to the calling app.
+ *
+ * <p>
+ * Apps can request notification listener access by sending the user to the activity that
+ * matches the system intent action
+ * {@link android.provider.Settings#ACTION_NOTIFICATION_LISTENER_SETTINGS}.
+ */
+ public boolean isNotificationListenerAccessGranted(ComponentName listener) {
+ INotificationManager service = getService();
+ try {
+ return service.isNotificationListenerAccessGranted(listener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isNotificationAssistantAccessGranted(ComponentName assistant) {
+ INotificationManager service = getService();
+ try {
+ return service.isNotificationAssistantAccessGranted(assistant);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/** @hide */
public boolean isNotificationPolicyAccessGrantedForPackage(String pkg) {
INotificationManager service = getService();
}
/**
+ * @hide
+ */
+ public List<String> getEnabledNotificationListenerPackages() {
+ INotificationManager service = getService();
+ try {
+ return service.getEnabledNotificationListenerPackages();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Gets the current notification policy.
*
* <p>
}
/** @hide */
- public ArraySet<String> getPackagesRequestingNotificationPolicyAccess() {
+ public void setNotificationListenerAccessGranted(ComponentName listener, boolean granted) {
INotificationManager service = getService();
try {
- final String[] pkgs = service.getPackagesRequestingNotificationPolicyAccess();
- if (pkgs != null && pkgs.length > 0) {
- final ArraySet<String> rt = new ArraySet<>(pkgs.length);
- for (int i = 0; i < pkgs.length; i++) {
- rt.add(pkgs[i]);
- }
- return rt;
- }
+ service.setNotificationListenerAccessGranted(listener, granted);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public void setNotificationListenerAccessGrantedForUser(ComponentName listener, int userId,
+ boolean granted) {
+ INotificationManager service = getService();
+ try {
+ service.setNotificationListenerAccessGrantedForUser(listener, userId, granted);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /** @hide */
+ public List<ComponentName> getEnabledNotificationListeners(int userId) {
+ INotificationManager service = getService();
+ try {
+ return service.getEnabledNotificationListeners(userId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
- return new ArraySet<>();
}
private Context mContext;
import android.app.AppOpsManager;
import android.app.Application;
import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.app.SearchManager;
import android.app.WallpaperManager;
import android.content.ComponentName;
public static final String ASSIST_DISCLOSURE_ENABLED = "assist_disclosure_enabled";
/**
- * Name of the service components that the current user has explicitly allowed to
+ * Read only list of the service components that the current user has explicitly allowed to
* see and assist with all of the user's notifications.
*
+ * @deprecated Use
+ * {@link NotificationManager#isNotificationListenerAccessGranted(ComponentName)}.
* @hide
*/
+ @Deprecated
public static final String ENABLED_NOTIFICATION_ASSISTANT =
"enabled_notification_assistant";
/**
- * Names of the service components that the current user has explicitly allowed to
+ * Read only list of the service components that the current user has explicitly allowed to
* see all of the user's notifications, separated by ':'.
*
* @hide
+ * @deprecated Use
+ * {@link NotificationManager#isNotificationAssistantAccessGranted(ComponentName)}.
*/
+ @Deprecated
public static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
/**
- * Names of the packages that the current user has explicitly allowed to
- * manage notification policy configuration, separated by ':'.
+ * Read only list of the packages that the current user has explicitly allowed to
+ * manage do not disturb, separated by ':'.
*
+ * @deprecated Use {@link NotificationManager#isNotificationPolicyAccessGranted()}.
* @hide
*/
+ @Deprecated
@TestApi
public static final String ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES =
"enabled_notification_policy_access_packages";
AUTOFILL_SERVICE,
ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
ENABLED_ACCESSIBILITY_SERVICES,
- ENABLED_NOTIFICATION_LISTENERS,
ENABLED_VR_LISTENERS,
ENABLED_INPUT_METHODS,
TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
/** @hide */
public static final String[] LEGACY_RESTORE_SETTINGS = {
+ ENABLED_NOTIFICATION_LISTENERS,
+ ENABLED_NOTIFICATION_ASSISTANT,
+ ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES
};
/**
package android.provider;
import static com.google.android.collect.Sets.newHashSet;
+
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.is;
+
import static java.lang.reflect.Modifier.isFinal;
import static java.lang.reflect.Modifier.isPublic;
import static java.lang.reflect.Modifier.isStatic;
-import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
/** Tests that ensure appropriate settings are backed up. */
@RunWith(AndroidJUnit4.class)
@SmallTest
Settings.Secure.DOZE_ALWAYS_ON,
Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION,
Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT,
+ Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES,
Settings.Secure.ENABLED_PRINT_SERVICES,
Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
}
if (currentVersion == 128) {
- // Version 128: Allow OEMs to grant DND access to default apps. Note that
- // the new apps are appended to the list of already approved apps.
- final SettingsState systemSecureSettings =
- getSecureSettingsLocked(userId);
-
- final Setting policyAccess = systemSecureSettings.getSettingLocked(
- Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES);
- String defaultPolicyAccess = getContext().getResources().getString(
- com.android.internal.R.string.config_defaultDndAccessPackages);
- if (!TextUtils.isEmpty(defaultPolicyAccess)) {
- if (policyAccess.isNull()) {
- systemSecureSettings.insertSettingLocked(
- Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES,
- defaultPolicyAccess, null, true,
- SettingsState.SYSTEM_PACKAGE_NAME);
- } else {
- StringBuilder currentSetting =
- new StringBuilder(policyAccess.getValue());
- currentSetting.append(":");
- currentSetting.append(defaultPolicyAccess);
- systemSecureSettings.updateSettingLocked(
- Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES,
- currentSetting.toString(), null, true,
- SettingsState.SYSTEM_PACKAGE_NAME);
- }
- }
-
+ // Version 128: Removed
currentVersion = 129;
}
}
if (currentVersion == 140) {
- // Version 141: One-time grant of notification listener privileges
- // to packages specified in overlay.
- String defaultListenerAccess = getContext().getResources().getString(
- com.android.internal.R.string.config_defaultListenerAccessPackages);
- if (defaultListenerAccess != null) {
- StringBuffer newListeners = new StringBuffer();
- for (String whitelistPkg : defaultListenerAccess.split(":")) {
- // Gather all notification listener components for candidate pkgs.
- Intent serviceIntent =
- new Intent(NotificationListenerService.SERVICE_INTERFACE)
- .setPackage(whitelistPkg);
- List<ResolveInfo> installedServices =
- getContext().getPackageManager().queryIntentServicesAsUser(
- serviceIntent,
- PackageManager.GET_SERVICES
- | PackageManager.GET_META_DATA
- | PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- userId);
-
- for (int i = 0, count = installedServices.size(); i < count; i++) {
- ResolveInfo resolveInfo = installedServices.get(i);
- ServiceInfo info = resolveInfo.serviceInfo;
- if (!android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE
- .equals(info.permission)) {
- continue;
- }
- newListeners.append(":")
- .append(info.getComponentName().flattenToString());
- }
- }
-
- if (newListeners.length() > 0) {
- final SettingsState secureSetting = getSecureSettingsLocked(userId);
- final Setting existingSetting = secureSetting.getSettingLocked(
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
- if (existingSetting.isNull()) {
- secureSetting.insertSettingLocked(
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
- newListeners.toString(), null, true,
- SettingsState.SYSTEM_PACKAGE_NAME);
- } else {
- StringBuilder currentSetting =
- new StringBuilder(existingSetting.getValue());
- currentSetting.append(newListeners.toString());
- secureSetting.updateSettingLocked(
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
- currentSetting.toString(), null, true,
- SettingsState.SYSTEM_PACKAGE_NAME);
- }
- }
- }
+ // Version 141: Removed
currentVersion = 141;
}
package com.android.server.media;
-import android.Manifest;
-import android.annotation.NonNull;
-import android.app.Activity;
import android.app.ActivityManager;
+import android.app.INotificationManager;
import android.app.KeyguardManager;
import android.app.PendingIntent;
import android.app.PendingIntent.CanceledException;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.speech.RecognizerIntent;
import android.text.TextUtils;
-import android.util.IntArray;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
/**
private AudioManagerInternal mAudioManagerInternal;
private ContentResolver mContentResolver;
private SettingsObserver mSettingsObserver;
+ private INotificationManager mNotificationManager;
// The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
// It's always not null after the MediaSessionService is started.
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
+ mNotificationManager = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
}
@Override
Log.d(TAG, "Checking if enabled notification listener " + compName);
}
if (compName != null) {
- final String enabledNotifListeners = Settings.Secure.getStringForUser(mContentResolver,
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
- userId);
- if (enabledNotifListeners != null) {
- final String[] components = enabledNotifListeners.split(":");
- for (int i = 0; i < components.length; i++) {
- final ComponentName component =
- ComponentName.unflattenFromString(components[i]);
- if (component != null) {
- if (compName.equals(component)) {
- if (DEBUG) {
- Log.d(TAG, "ok to get sessions. " + component +
- " is authorized notification listener");
- }
- return true;
- }
- }
- }
- }
- if (DEBUG) {
- Log.d(TAG, "not ok to get sessions. " + compName +
- " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
+ try {
+ return mNotificationManager.isNotificationListenerAccessGrantedForUser(
+ compName, userId);
+ } catch(RemoteException e) {
+ Log.w(TAG, "Dead NotificationManager in isEnabledNotificationListener", e);
}
}
return false;
package com.android.server.notification;
-import android.annotation.NonNull;
import android.app.INotificationManager;
import android.app.NotificationManager;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
+import android.content.pm.IPackageManager;
import android.net.Uri;
-import android.os.Handler;
import android.os.IBinder;
import android.os.IInterface;
import android.os.RemoteException;
import android.service.notification.Condition;
import android.service.notification.ConditionProviderService;
import android.service.notification.IConditionProvider;
-import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import java.util.Arrays;
public class ConditionProviders extends ManagedServices {
+
+ private static final String TAG_ENABLED_DND_APPS = "dnd_apps";
+
private final ArrayList<ConditionRecord> mRecords = new ArrayList<>();
private final ArraySet<String> mSystemConditionProviderNames;
private final ArraySet<SystemConditionProviderService> mSystemConditionProviders
private Callback mCallback;
- public ConditionProviders(Context context, Handler handler, UserProfiles userProfiles) {
- super(context, handler, new Object(), userProfiles);
+ public ConditionProviders(Context context, UserProfiles userProfiles, IPackageManager pm) {
+ super(context, new Object(), userProfiles, pm);
mSystemConditionProviderNames = safeSet(PropConfig.getStringArray(mContext,
"system.condition.providers",
R.array.config_system_condition_providers));
+ mApprovalLevel = APPROVAL_BY_PACKAGE;
}
public void setCallback(Callback callback) {
c.caption = "condition provider";
c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE;
c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES;
+ c.managedServiceTypeTag = TAG_ENABLED_DND_APPS;
c.secondarySettingName = Settings.Secure.ENABLED_NOTIFICATION_LISTENERS;
c.bindPermission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE;
c.settingsAction = Settings.ACTION_CONDITION_PROVIDER_SETTINGS;
}
@Override
- public void onPackagesChanged(boolean removingPackage, String[] pkgList) {
+ public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uid) {
if (removingPackage) {
INotificationManager inm = NotificationManager.getService();
}
}
}
- super.onPackagesChanged(removingPackage, pkgList);
+ super.onPackagesChanged(removingPackage, pkgList, uid);
}
public ManagedServiceInfo checkServiceToken(IConditionProvider provider) {
}
}
- @Override
- protected @NonNull ArraySet<ComponentName> loadComponentNamesFromSetting(String settingName,
- int userId) {
- final ContentResolver cr = mContext.getContentResolver();
- String settingValue = Settings.Secure.getStringForUser(
- cr,
- settingName,
- userId);
- if (TextUtils.isEmpty(settingValue))
- return new ArraySet<>();
- String[] packages = settingValue.split(ENABLED_SERVICES_SEPARATOR);
- ArraySet<ComponentName> result = new ArraySet<>(packages.length);
- for (int i = 0; i < packages.length; i++) {
- if (!TextUtils.isEmpty(packages[i])) {
- final ComponentName component = ComponentName.unflattenFromString(packages[i]);
- if (component != null) {
- result.addAll(queryPackageForServices(component.getPackageName(), userId));
- } else {
- result.addAll(queryPackageForServices(packages[i], userId));
- }
- }
- }
- return result;
- }
-
public boolean subscribeIfNecessary(ComponentName component, Uri conditionId) {
synchronized (mMutex) {
final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
import android.app.ActivityManager;
import android.app.PendingIntent;
import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
-import android.database.ContentObserver;
-import android.net.Uri;
import android.os.Binder;
import android.os.Build;
-import android.os.Handler;
import android.os.IBinder;
import android.os.IInterface;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
import com.android.server.notification.NotificationManagerService.DumpFilter;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
-import java.util.Objects;
import java.util.Set;
+import java.util.stream.Collectors;
/**
* Manages the lifecycle of application-provided services bound by system server.
protected static final String ENABLED_SERVICES_SEPARATOR = ":";
+ /**
+ * List of components and apps that can have running {@link ManagedServices}.
+ */
+ static final String TAG_MANAGED_SERVICES = "service_listing";
+ static final String ATT_APPROVED_LIST = "approved";
+ static final String ATT_USER_ID = "user";
+ static final String ATT_IS_PRIMARY = "primary";
+
+ static final int APPROVAL_BY_PACKAGE = 0;
+ static final int APPROVAL_BY_COMPONENT = 1;
+
protected final Context mContext;
protected final Object mMutex;
private final UserProfiles mUserProfiles;
- private final SettingsObserver mSettingsObserver;
private final IPackageManager mPm;
private final Config mConfig;
- private ArraySet<String> mRestored;
// contains connections to all connected services, including app services
// and system services
- private final ArrayList<ManagedServiceInfo> mServices = new ArrayList<ManagedServiceInfo>();
+ private final ArrayList<ManagedServiceInfo> mServices = new ArrayList<>();
// things that will be put into mServices as soon as they're ready
- private final ArrayList<String> mServicesBinding = new ArrayList<String>();
+ private final ArrayList<String> mServicesBinding = new ArrayList<>();
// lists the component names of all enabled (and therefore potentially connected)
// app services for current profiles.
private ArraySet<ComponentName> mEnabledServicesForCurrentProfiles
- = new ArraySet<ComponentName>();
+ = new ArraySet<>();
// Just the packages from mEnabledServicesForCurrentProfiles
- private ArraySet<String> mEnabledServicesPackageNames = new ArraySet<String>();
- // List of packages in restored setting across all mUserProfiles, for quick
- // filtering upon package updates.
- private ArraySet<String> mRestoredPackages = new ArraySet<>();
+ private ArraySet<String> mEnabledServicesPackageNames = new ArraySet<>();
// List of enabled packages that have nevertheless asked not to be run
private ArraySet<ComponentName> mSnoozingForCurrentProfiles = new ArraySet<>();
+ // List of approved packages or components (by user, then by primary/secondary) that are
+ // allowed to be bound as managed services. A package or component appearing in this list does
+ // not mean that we are currently bound to said package/component.
+ private ArrayMap<Integer, ArrayMap<Boolean, ArraySet<String>>> mApproved = new ArrayMap<>();
// Kept to de-dupe user change events (experienced after boot, when we receive a settings and a
// user change).
private int[] mLastSeenProfileIds;
- private final BroadcastReceiver mRestoreReceiver;
+ // True if approved services are stored in xml, not settings.
+ private boolean mUseXml;
- public ManagedServices(Context context, Handler handler, Object mutex,
- UserProfiles userProfiles) {
+ // Whether managed services are approved individually or package wide
+ protected int mApprovalLevel;
+
+ public ManagedServices(Context context, Object mutex, UserProfiles userProfiles,
+ IPackageManager pm) {
mContext = context;
mMutex = mutex;
mUserProfiles = userProfiles;
- mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
+ mPm = pm;
mConfig = getConfig();
- mSettingsObserver = new SettingsObserver(handler);
-
- mRestoreReceiver = new SettingRestoredReceiver();
- IntentFilter filter = new IntentFilter(Intent.ACTION_SETTING_RESTORED);
- context.registerReceiver(mRestoreReceiver, filter);
- rebuildRestoredPackages();
- }
-
- class SettingRestoredReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_SETTING_RESTORED.equals(intent.getAction())) {
- String element = intent.getStringExtra(Intent.EXTRA_SETTING_NAME);
- if (Objects.equals(element, mConfig.secureSettingName)
- || Objects.equals(element, mConfig.secondarySettingName)) {
- String prevValue = intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE);
- String newValue = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE);
- settingRestored(element, prevValue, newValue, getSendingUserId());
- }
- }
- }
+ mApprovalLevel = APPROVAL_BY_COMPONENT;
}
abstract protected Config getConfig();
protected void onServiceRemovedLocked(ManagedServiceInfo removed) { }
private ManagedServiceInfo newServiceInfo(IInterface service,
- ComponentName component, int userid, boolean isSystem, ServiceConnection connection,
+ ComponentName component, int userId, boolean isSystem, ServiceConnection connection,
int targetSdkVersion) {
- return new ManagedServiceInfo(service, component, userid, isSystem, connection,
+ return new ManagedServiceInfo(service, component, userId, isSystem, connection,
targetSdkVersion);
}
- public void onBootPhaseAppsCanStart() {
- mSettingsObserver.observe();
- }
+ public void onBootPhaseAppsCanStart() {}
public void dump(PrintWriter pw, DumpFilter filter) {
+ pw.println(" Allowed " + getCaption() + "s:");
+ final int N = mApproved.size();
+ for (int i = 0 ; i < N; i++) {
+ final int userId = mApproved.keyAt(i);
+ final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.valueAt(i);
+ if (approvedByType != null) {
+ final int M = approvedByType.size();
+ for (int j = 0; j < M; j++) {
+ final boolean isPrimary = approvedByType.keyAt(j);
+ final ArraySet<String> approved = approvedByType.valueAt(j);
+ if (approvedByType != null && approvedByType.size() > 0) {
+ pw.println(" " + String.join(ENABLED_SERVICES_SEPARATOR, approved)
+ + " (user: " + userId + " isPrimary: " + isPrimary + ")");
+ }
+ }
+ }
+ }
+
pw.println(" All " + getCaption() + "s (" + mEnabledServicesForCurrentProfiles.size()
+ ") enabled for current profiles:");
for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
}
}
- // By convention, restored settings are replicated to another settings
- // entry, named similarly but with a disambiguation suffix.
- public static String restoredSettingName(String setting) {
- return setting + ":restored";
- }
-
- // The OS has done a restore of this service's saved state. We clone it to the
- // 'restored' reserve, and then once we return and the actual write to settings is
- // performed, our observer will do the work of maintaining the restored vs live
- // settings data.
- public void settingRestored(String element, String oldValue, String newValue, int userid) {
- if (DEBUG) Slog.d(TAG, "Restored managed service setting: " + element
- + " ovalue=" + oldValue + " nvalue=" + newValue);
- if (mConfig.secureSettingName.equals(element) ||
- mConfig.secondarySettingName.equals(element)) {
- if (element != null) {
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
- restoredSettingName(element),
- newValue,
- userid);
- if (mConfig.secureSettingName.equals(element)) {
- updateSettingsAccordingToInstalledServices(element, userid);
+ protected void onSettingRestored(String element, String value, int userId) {
+ if (!mUseXml) {
+ Slog.d(TAG, "Restored managed service setting: " + element);
+ if (mConfig.secureSettingName.equals(element) ||
+ (mConfig.secondarySettingName != null
+ && mConfig.secondarySettingName.equals(element))) {
+ Settings.Secure.putStringForUser(
+ mContext.getContentResolver(), element, value, userId);
+ loadAllowedComponentsFromSettings();
+ rebindServices(false);
+ }
+ }
+ }
+
+ public void writeXml(XmlSerializer out, boolean forBackup) throws IOException {
+ out.startTag(null, getConfig().managedServiceTypeTag);
+
+ if (forBackup) {
+ trimApprovedListsAccordingToInstalledServices();
+ }
+
+ final int N = mApproved.size();
+ for (int i = 0 ; i < N; i++) {
+ final int userId = mApproved.keyAt(i);
+ final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.valueAt(i);
+ if (approvedByType != null) {
+ final int M = approvedByType.size();
+ for (int j = 0; j < M; j++) {
+ final boolean isPrimary = approvedByType.keyAt(j);
+ final Set<String> approved = approvedByType.valueAt(j);
+ if (approved != null && approved.size() > 0) {
+ String allowedItems = String.join(ENABLED_SERVICES_SEPARATOR, approved);
+ out.startTag(null, TAG_MANAGED_SERVICES);
+ out.attribute(null, ATT_APPROVED_LIST, allowedItems);
+ out.attribute(null, ATT_USER_ID, Integer.toString(userId));
+ out.attribute(null, ATT_IS_PRIMARY, Boolean.toString(isPrimary));
+ out.endTag(null, TAG_MANAGED_SERVICES);
+
+ if (!forBackup && isPrimary) {
+ // Also write values to settings, for observers who haven't migrated yet
+ Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ getConfig().secureSettingName, allowedItems, userId);
+ }
+
+ }
+ }
+ }
+ }
+
+ out.endTag(null, getConfig().managedServiceTypeTag);
+ }
+
+ /**
+ * @return false if modifications were made to the data on load that requires the xml file
+ * to be re-written
+ */
+ public boolean readXml(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ boolean rewriteXml = false;
+ int type = parser.getEventType();
+ String tag = parser.getName();
+ if (type != XmlPullParser.START_TAG || !getConfig().managedServiceTypeTag.equals(tag)) {
+ // xml empty/invalid - read from setting instead
+ loadAllowedComponentsFromSettings();
+ rewriteXml = true;
+ } else {
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ tag = parser.getName();
+ if (type == XmlPullParser.END_TAG
+ && getConfig().managedServiceTypeTag.equals(tag)) {
+ break;
}
- rebuildRestoredPackages();
+ if (type == XmlPullParser.START_TAG) {
+ if (TAG_MANAGED_SERVICES.equals(tag)) {
+ final String approved = XmlUtils.safeString(parser, ATT_APPROVED_LIST, "");
+ final int userId = XmlUtils.safeInt(parser, ATT_USER_ID, 0);
+ final boolean isPrimary = XmlUtils.safeBool(parser, ATT_IS_PRIMARY, true);
+ addApprovedList(approved, userId, isPrimary);
+ }
+ }
+ }
+ mUseXml = true;
+ }
+ rebindServices(false);
+
+ return rewriteXml;
+ }
+
+ private void loadAllowedComponentsFromSettings() {
+
+ UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ for (UserInfo user : userManager.getUsers()) {
+ final ContentResolver cr = mContext.getContentResolver();
+ addApprovedList(Settings.Secure.getStringForUser(
+ cr,
+ getConfig().secureSettingName,
+ user.id), user.id, true);
+ if (!TextUtils.isEmpty(getConfig().secondarySettingName)) {
+ addApprovedList(Settings.Secure.getStringForUser(
+ cr,
+ getConfig().secondarySettingName,
+ user.id), user.id, false);
+ }
+ }
+ Slog.d(TAG, "Done loading approved values from settings");
+ }
+
+ private void addApprovedList(String approved, int userId, boolean isPrimary) {
+ if (TextUtils.isEmpty(approved)) {
+ approved = "";
+ }
+ ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.get(userId);
+ if (approvedByType == null) {
+ approvedByType = new ArrayMap<>();
+ mApproved.put(userId, approvedByType);
+ }
+ String[] approvedArray = approved.split(ENABLED_SERVICES_SEPARATOR);
+ final ArraySet<String> approvedList = new ArraySet<>();
+ for (String pkgOrComponent : approvedArray) {
+ String approvedItem = getApprovedValue(pkgOrComponent);
+ if (approvedItem != null) {
+ approvedList.add(approvedItem);
}
}
+ approvedByType.put(isPrimary, approvedList);
}
- public boolean isComponentEnabledForPackage(String pkg) {
+ protected boolean isComponentEnabledForPackage(String pkg) {
return mEnabledServicesPackageNames.contains(pkg);
}
- public void onPackagesChanged(boolean removingPackage, String[] pkgList) {
+ protected void setPackageOrComponentEnabled(String pkgOrComponent, int userId,
+ boolean isPrimary, boolean enabled) {
+ ArrayMap<Boolean, ArraySet<String>> allowedByType = mApproved.get(userId);
+ if (allowedByType == null) {
+ allowedByType = new ArrayMap<>();
+ mApproved.put(userId, allowedByType);
+ }
+ ArraySet<String> approved = allowedByType.get(isPrimary);
+ if (approved == null) {
+ approved = new ArraySet<>();
+ allowedByType.put(isPrimary, approved);
+ }
+ String approvedItem = getApprovedValue(pkgOrComponent);
+
+ if (approvedItem != null) {
+ if (enabled) {
+ approved.add(approvedItem);
+ } else {
+ approved.remove(approvedItem);
+ }
+ }
+
+ rebindServices(false);
+ }
+
+ private String getApprovedValue(String pkgOrComponent) {
+ if (mApprovalLevel == APPROVAL_BY_COMPONENT) {
+ if(ComponentName.unflattenFromString(pkgOrComponent) != null) {
+ return pkgOrComponent;
+ }
+ return null;
+ } else {
+ return getPackageName(pkgOrComponent);
+ }
+ }
+
+ protected List<ComponentName> getAllowedComponents(int userId) {
+ final List<ComponentName> allowedComponents = new ArrayList<>();
+ final ArrayMap<Boolean, ArraySet<String>> allowedByType =
+ mApproved.getOrDefault(userId, new ArrayMap<>());
+ for (int i = 0; i < allowedByType.size(); i++) {
+ final ArraySet<String> allowed = allowedByType.valueAt(i);
+ allowedComponents.addAll(allowed.stream().map(ComponentName::unflattenFromString)
+ .filter(out -> out != null).collect(Collectors.toList()));
+ }
+ return allowedComponents;
+ }
+
+ protected List<String> getAllowedPackages(int userId) {
+ final List<String> allowedPackages = new ArrayList<>();
+ final ArrayMap<Boolean, ArraySet<String>> allowedByType =
+ mApproved.getOrDefault(userId, new ArrayMap<>());
+ for (int i = 0; i < allowedByType.size(); i++) {
+ final ArraySet<String> allowed = allowedByType.valueAt(i);
+ allowedPackages.addAll(
+ allowed.stream().map(this::getPackageName).collect(Collectors.toList()));
+ }
+ return allowedPackages;
+ }
+
+ protected boolean isPackageOrComponentAllowed(String pkgOrComponent, int userId) {
+ ArrayMap<Boolean, ArraySet<String>> allowedByType =
+ mApproved.getOrDefault(userId, new ArrayMap<>());
+ for (int i = 0; i < allowedByType.size(); i++) {
+ ArraySet<String> allowed = allowedByType.valueAt(i);
+ if (allowed.contains(pkgOrComponent)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uidList) {
if (DEBUG) Slog.d(TAG, "onPackagesChanged removingPackage=" + removingPackage
+ " pkgList=" + (pkgList == null ? null : Arrays.asList(pkgList))
+ " mEnabledServicesPackageNames=" + mEnabledServicesPackageNames);
- boolean anyServicesInvolved = false;
if (pkgList != null && (pkgList.length > 0)) {
+ boolean anyServicesInvolved = false;
+ // Remove notification settings for uninstalled package
+ if (removingPackage) {
+ int size = Math.min(pkgList.length, uidList.length);
+ for (int i = 0; i < size; i++) {
+ final String pkg = pkgList[i];
+ final int userId = UserHandle.getUserId(uidList[i]);
+ anyServicesInvolved = removeUninstalledItemsFromApprovedLists(userId, pkg);
+ }
+ }
for (String pkgName : pkgList) {
- if (mEnabledServicesPackageNames.contains(pkgName) ||
- mRestoredPackages.contains(pkgName)) {
+ if (mEnabledServicesPackageNames.contains(pkgName)) {
anyServicesInvolved = true;
}
}
- }
- if (anyServicesInvolved) {
- // if we're not replacing a package, clean up orphaned bits
- if (removingPackage) {
- updateSettingsAccordingToInstalledServices();
- rebuildRestoredPackages();
+ if (anyServicesInvolved) {
+ // make sure we're still bound to any of our services who may have just upgraded
+ rebindServices(false);
}
- // make sure we're still bound to any of our services who may have just upgraded
- rebindServices(false);
}
}
public void onUserSwitched(int user) {
if (DEBUG) Slog.d(TAG, "onUserSwitched u=" + user);
- rebuildRestoredPackages();
if (Arrays.equals(mLastSeenProfileIds, mUserProfiles.getCurrentProfileIds())) {
if (DEBUG) Slog.d(TAG, "Current profile IDs didn't change, skipping rebindServices().");
return;
public void onUserUnlocked(int user) {
if (DEBUG) Slog.d(TAG, "onUserUnlocked u=" + user);
- rebuildRestoredPackages();
rebindServices(false);
}
- public ManagedServiceInfo getServiceFromTokenLocked(IInterface service) {
+ private ManagedServiceInfo getServiceFromTokenLocked(IInterface service) {
if (service == null) {
return null;
}
return null;
}
- public ManagedServiceInfo checkServiceTokenLocked(IInterface service) {
+ protected ManagedServiceInfo checkServiceTokenLocked(IInterface service) {
checkNotNull(service);
ManagedServiceInfo info = getServiceFromTokenLocked(service);
if (info != null) {
/**
* Add a service to our callbacks. The lifecycle of this service is managed externally,
- * but unlike a system service, it should not be considered privledged.
+ * but unlike a system service, it should not be considered privileged.
* */
- public void registerGuestService(ManagedServiceInfo guest) {
+ protected void registerGuestService(ManagedServiceInfo guest) {
checkNotNull(guest.service);
if (!checkType(guest.service)) {
throw new IllegalArgumentException();
}
}
- public void setComponentState(ComponentName component, boolean enabled) {
+ protected void setComponentState(ComponentName component, boolean enabled) {
boolean previous = !mSnoozingForCurrentProfiles.contains(component);
if (previous == enabled) {
return;
component.flattenToShortString());
}
-
synchronized (mMutex) {
final int[] userIds = mUserProfiles.getCurrentProfileIds();
}
}
- private void rebuildRestoredPackages() {
- mRestoredPackages.clear();
- String secureSettingName = restoredSettingName(mConfig.secureSettingName);
- String secondarySettingName = mConfig.secondarySettingName == null
- ? null : restoredSettingName(mConfig.secondarySettingName);
- int[] userIds = mUserProfiles.getCurrentProfileIds();
- final int N = userIds.length;
- for (int i = 0; i < N; ++i) {
- ArraySet<ComponentName> names =
- loadComponentNamesFromSetting(secureSettingName, userIds[i]);
- if (secondarySettingName != null) {
- names.addAll(loadComponentNamesFromSetting(secondarySettingName, userIds[i]));
- }
- for (ComponentName name : names) {
- mRestoredPackages.add(name.getPackageName());
- }
- }
- }
-
-
- protected @NonNull ArraySet<ComponentName> loadComponentNamesFromSetting(String settingName,
- int userId) {
- final ContentResolver cr = mContext.getContentResolver();
- String settingValue = Settings.Secure.getStringForUser(
- cr,
- settingName,
- userId);
- if (TextUtils.isEmpty(settingValue))
+ private @NonNull ArraySet<ComponentName> loadComponentNamesFromValues(
+ ArraySet<String> approved, int userId) {
+ if (approved == null || approved.size() == 0)
return new ArraySet<>();
- String[] restored = settingValue.split(ENABLED_SERVICES_SEPARATOR);
- ArraySet<ComponentName> result = new ArraySet<>(restored.length);
- for (int i = 0; i < restored.length; i++) {
- ComponentName value = ComponentName.unflattenFromString(restored[i]);
- if (null != value) {
- result.add(value);
+ ArraySet<ComponentName> result = new ArraySet<>(approved.size());
+ for (int i = 0; i < approved.size(); i++) {
+ final String packageOrComponent = approved.valueAt(i);
+ if (!TextUtils.isEmpty(packageOrComponent)) {
+ ComponentName component = ComponentName.unflattenFromString(packageOrComponent);
+ if (component != null) {
+ result.add(component);
+ } else {
+ result.addAll(queryPackageForServices(packageOrComponent, userId));
+ }
}
}
return result;
}
- private void storeComponentsToSetting(Set<ComponentName> components,
- String settingName,
- int userId) {
- String[] componentNames = null;
- if (null != components) {
- componentNames = new String[components.size()];
- int index = 0;
- for (ComponentName c: components) {
- componentNames[index++] = c.flattenToString();
- }
- }
- final String value = (componentNames == null) ? "" :
- TextUtils.join(ENABLED_SERVICES_SEPARATOR, componentNames);
- final ContentResolver cr = mContext.getContentResolver();
- Settings.Secure.putStringForUser(
- cr,
- settingName,
- value,
- userId);
- }
-
- /**
- * Remove access for any services that no longer exist.
- */
- private void updateSettingsAccordingToInstalledServices() {
- int[] userIds = mUserProfiles.getCurrentProfileIds();
- final int N = userIds.length;
- for (int i = 0; i < N; ++i) {
- updateSettingsAccordingToInstalledServices(mConfig.secureSettingName, userIds[i]);
- if (mConfig.secondarySettingName != null) {
- updateSettingsAccordingToInstalledServices(
- mConfig.secondarySettingName, userIds[i]);
- }
- }
- rebuildRestoredPackages();
+ protected Set<ComponentName> queryPackageForServices(String packageName, int userId) {
+ return queryPackageForServices(packageName, 0, userId);
}
- protected Set<ComponentName> queryPackageForServices(String packageName, int userId) {
+ protected Set<ComponentName> queryPackageForServices(String packageName, int extraFlags,
+ int userId) {
Set<ComponentName> installed = new ArraySet<>();
final PackageManager pm = mContext.getPackageManager();
Intent queryIntent = new Intent(mConfig.serviceInterface);
}
List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
queryIntent,
- PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA | extraFlags,
userId);
if (DEBUG)
Slog.v(TAG, mConfig.serviceInterface + " services: " + installedServices);
return installed;
}
- private void updateSettingsAccordingToInstalledServices(String setting, int userId) {
- boolean restoredChanged = false;
- boolean currentChanged = false;
- Set<ComponentName> restored =
- loadComponentNamesFromSetting(restoredSettingName(setting), userId);
- Set<ComponentName> current =
- loadComponentNamesFromSetting(setting, userId);
- // Load all services for all packages.
- Set<ComponentName> installed = queryPackageForServices(null, userId);
-
- ArraySet<ComponentName> retained = new ArraySet<>();
-
- for (ComponentName component : installed) {
- if (null != restored) {
- boolean wasRestored = restored.remove(component);
- if (wasRestored) {
- // Freshly installed package has service that was mentioned in restored setting.
- if (DEBUG)
- Slog.v(TAG, "Restoring " + component + " for user " + userId);
- restoredChanged = true;
- currentChanged = true;
- retained.add(component);
- continue;
+ private void trimApprovedListsAccordingToInstalledServices() {
+ int N = mApproved.size();
+ for (int i = 0 ; i < N; i++) {
+ final int userId = mApproved.keyAt(i);
+ final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.valueAt(i);
+ int M = approvedByType.size();
+ for (int j = 0; j < M; j++) {
+ final ArraySet<String> approved = approvedByType.valueAt(j);
+ int P = approved.size();
+ for (int k = P - 1; k >= 0; k--) {
+ final String approvedPackageOrComponent = approved.valueAt(k);
+ if (!hasMatchingServices(approvedPackageOrComponent, userId)){
+ approved.removeAt(k);
+ if (DEBUG) {
+ Slog.v(TAG, "Removing " + approvedPackageOrComponent
+ + " from approved list; no matching services found");
+ }
+ } else {
+ if (DEBUG) {
+ Slog.v(TAG, "Keeping " + approvedPackageOrComponent
+ + " on approved list; matching services found");
+ }
+ }
}
}
+ }
+ }
- if (null != current) {
- if (current.contains(component))
- retained.add(component);
+ private boolean removeUninstalledItemsFromApprovedLists(int uninstalledUserId, String pkg) {
+ boolean removed = false;
+ final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.get(uninstalledUserId);
+ if (approvedByType != null) {
+ int M = approvedByType.size();
+ for (int j = 0; j < M; j++) {
+ final ArraySet<String> approved = approvedByType.valueAt(j);
+ int O = approved.size();
+ for (int k = O - 1; k >= 0; k--) {
+ final String packageOrComponent = approved.valueAt(k);
+ final String packageName = getPackageName(packageOrComponent);
+ if (TextUtils.equals(pkg, packageName)) {
+ approved.removeAt(k);
+ if (DEBUG) {
+ Slog.v(TAG, "Removing " + packageOrComponent
+ + " from approved list; uninstalled");
+ }
+ }
+ }
}
}
+ return removed;
+ }
- currentChanged |= ((current == null ? 0 : current.size()) != retained.size());
-
- if (currentChanged) {
- if (DEBUG) Slog.v(TAG, "List of " + getCaption() + " services was updated " + current);
- storeComponentsToSetting(retained, setting, userId);
+ protected String getPackageName(String packageOrComponent) {
+ final ComponentName component = ComponentName.unflattenFromString(packageOrComponent);
+ if (component != null) {
+ return component.getPackageName();
+ } else {
+ return packageOrComponent;
}
+ }
- if (restoredChanged) {
- if (DEBUG) Slog.v(TAG,
- "List of " + getCaption() + " restored services was updated " + restored);
- storeComponentsToSetting(restored, restoredSettingName(setting), userId);
+ private boolean hasMatchingServices(String packageOrComponent, int userId) {
+ if (!TextUtils.isEmpty(packageOrComponent)) {
+ final String packageName = getPackageName(packageOrComponent);
+ return queryPackageForServices(packageName, userId).size() > 0;
}
+ return false;
}
/**
final SparseArray<ArraySet<ComponentName>> componentsByUser = new SparseArray<>();
for (int i = 0; i < nUserIds; ++i) {
- componentsByUser.put(userIds[i],
- loadComponentNamesFromSetting(mConfig.secureSettingName, userIds[i]));
- if (mConfig.secondarySettingName != null) {
- componentsByUser.get(userIds[i]).addAll(
- loadComponentNamesFromSetting(mConfig.secondarySettingName, userIds[i]));
+ final int userId = userIds[i];
+ final ArrayMap<Boolean, ArraySet<String>> approvedLists = mApproved.get(userIds[i]);
+ if (approvedLists != null) {
+ final int N = approvedLists.size();
+ for (int j = 0; j < N; j++) {
+ ArraySet<ComponentName> approvedByUser = componentsByUser.get(userId);
+ if (approvedByUser == null) {
+ approvedByUser = new ArraySet<>();
+ componentsByUser.put(userId, approvedByUser);
+ }
+ approvedByUser.addAll(
+ loadComponentNamesFromValues(approvedLists.valueAt(j), userId));
+ }
}
}
// decode the list of components
final ArraySet<ComponentName> userComponents = componentsByUser.get(userIds[i]);
if (null == userComponents) {
- toAdd.put(userIds[i], new ArraySet<ComponentName>());
+ toAdd.put(userIds[i], new ArraySet<>());
continue;
}
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userIds[i]);
if (info == null || !mConfig.bindPermission.equals(info.permission)) {
- Slog.w(TAG, "Skipping " + getCaption() + " service " + component
+ Slog.w(TAG, "Not binding " + getCaption() + " service " + component
+ ": it does not require the permission " + mConfig.bindPermission);
continue;
}
}
} catch (SecurityException ex) {
Slog.e(TAG, "Unable to bind " + getCaption() + " service: " + intent, ex);
- return;
}
}
}
}
- private class SettingsObserver extends ContentObserver {
- private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(mConfig.secureSettingName);
- private final Uri mSecondarySettingsUri;
-
- private SettingsObserver(Handler handler) {
- super(handler);
- if (mConfig.secondarySettingName != null) {
- mSecondarySettingsUri = Settings.Secure.getUriFor(mConfig.secondarySettingName);
- } else {
- mSecondarySettingsUri = null;
- }
- }
-
- private void observe() {
- ContentResolver resolver = mContext.getContentResolver();
- resolver.registerContentObserver(mSecureSettingsUri,
- false, this, UserHandle.USER_ALL);
- if (mSecondarySettingsUri != null) {
- resolver.registerContentObserver(mSecondarySettingsUri,
- false, this, UserHandle.USER_ALL);
- }
- update(null);
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- update(uri);
- }
-
- private void update(Uri uri) {
- if (uri == null || mSecureSettingsUri.equals(uri)
- || uri.equals(mSecondarySettingsUri)) {
- if (DEBUG) Slog.d(TAG, "Setting changed: uri=" + uri);
- rebindServices(false);
- rebuildRestoredPackages();
- }
- }
- }
-
public class ManagedServiceInfo implements IBinder.DeathRecipient {
public IInterface service;
public ComponentName component;
public String serviceInterface;
public String secureSettingName;
public String secondarySettingName;
+ public String managedServiceTypeTag;
public String bindPermission;
public String settingsAction;
public int clientLabel;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ParceledListSlice;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.os.ServiceManager;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
-import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
/** {@hide} */
final ArrayMap<Integer, ArrayMap<String, String>> mAutobundledSummaries = new ArrayMap<>();
final ArrayList<ToastRecord> mToastQueue = new ArrayList<ToastRecord>();
final ArrayMap<String, NotificationRecord> mSummaryByGroupKey = new ArrayMap<>();
- final PolicyAccess mPolicyAccess = new PolicyAccess();
// The last key in this list owns the hardware.
ArrayList<String> mLights = new ArrayList<>();
}
+ protected void readDefaultApprovedServices() {
+ final int userId = UserHandle.USER_SYSTEM;
+ String defaultListenerAccess = getContext().getResources().getString(
+ com.android.internal.R.string.config_defaultListenerAccessPackages);
+ if (defaultListenerAccess != null) {
+ for (String whitelisted :
+ defaultListenerAccess.split(ManagedServices.ENABLED_SERVICES_SEPARATOR)) {
+ // Gather all notification listener components for candidate pkgs.
+ Set<ComponentName> approvedListeners =
+ mListeners.queryPackageForServices(whitelisted,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+ for (ComponentName cn : approvedListeners) {
+ try {
+ getBinderService().setNotificationListenerAccessGrantedForUser(cn,
+ userId, true);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+ String defaultDndAccess = getContext().getResources().getString(
+ com.android.internal.R.string.config_defaultDndAccessPackages);
+ if (defaultListenerAccess != null) {
+ for (String whitelisted :
+ defaultDndAccess.split(ManagedServices.ENABLED_SERVICES_SEPARATOR)) {
+ try {
+ getBinderService().setNotificationPolicyAccessGranted(whitelisted, true);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
private void readPolicyXml(InputStream stream, boolean forRestore)
throws XmlPullParserException, NumberFormatException, IOException {
final XmlPullParser parser = Xml.newPullParser();
parser.setInput(stream, StandardCharsets.UTF_8.name());
+ boolean saveXml = false;
while (parser.next() != END_DOCUMENT) {
mZenModeHelper.readXml(parser, forRestore);
mRankingHelper.readXml(parser, forRestore);
+ saveXml |= mListeners.readXml(parser);
+ saveXml |= mNotificationAssistants.readXml(parser);
+ saveXml |= mConditionProviders.readXml(parser);
+ }
+
+ if (saveXml) {
+ savePolicyFile();
}
}
readPolicyXml(infile, false /*forRestore*/);
} catch (FileNotFoundException e) {
// No data yet
+ // Load default managed services approvals
+ readDefaultApprovedServices();
} catch (IOException e) {
Log.wtf(TAG, "Unable to read notification policy", e);
} catch (NumberFormatException e) {
out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
mZenModeHelper.writeXml(out, forBackup);
mRankingHelper.writeXml(out, forBackup);
+ mListeners.writeXml(out, forBackup);
+ mNotificationAssistants.writeXml(out, forBackup);
+ mConditionProviders.writeXml(out, forBackup);
out.endTag(null, TAG_NOTIFICATION_POLICY);
out.endDocument();
}
updateLightsLocked();
}
+ private final BroadcastReceiver mRestoreReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_SETTING_RESTORED.equals(intent.getAction())) {
+ try {
+ String element = intent.getStringExtra(Intent.EXTRA_SETTING_NAME);
+ String newValue = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE);
+ mListeners.onSettingRestored(element, newValue, getSendingUserId());
+ mConditionProviders.onSettingRestored(element, newValue, getSendingUserId());
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Cannot restore managed services from settings", e);
+ }
+ }
+ }
+ };
+
private final BroadcastReceiver mNotificationTimeoutReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
}
}
}
- mListeners.onPackagesChanged(removingPackage, pkgList);
- mNotificationAssistants.onPackagesChanged(removingPackage, pkgList);
- mConditionProviders.onPackagesChanged(removingPackage, pkgList);
+ mListeners.onPackagesChanged(removingPackage, pkgList, uidList);
+ mNotificationAssistants.onPackagesChanged(removingPackage, pkgList, uidList);
+ mConditionProviders.onPackagesChanged(removingPackage, pkgList, uidList);
mRankingHelper.onPackagesChanged(removingPackage, changeUserId, pkgList, uidList);
savePolicyFile();
}
@VisibleForTesting
void init(Looper looper, IPackageManager packageManager, PackageManager packageManagerClient,
LightsManager lightsManager, NotificationListeners notificationListeners,
+ NotificationAssistants notificationAssistants, ConditionProviders conditionProviders,
ICompanionDeviceManager companionManager, SnoozeHelper snoozeHelper,
- NotificationUsageStats usageStats) {
+ NotificationUsageStats usageStats, AtomicFile policyFile) {
Resources resources = getContext().getResources();
mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(),
Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE,
mRankingHandler,
mUsageStats,
extractorNames);
- mConditionProviders = new ConditionProviders(getContext(), mHandler, mUserProfiles);
+ mConditionProviders = conditionProviders;
mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders);
mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
@Override
}
});
- final File systemDir = new File(Environment.getDataDirectory(), "system");
- mPolicyFile = new AtomicFile(new File(systemDir, "notification_policy.xml"));
-
- loadPolicyFile();
-
// This is a ManagedServices object that keeps track of the listeners.
mListeners = notificationListeners;
// This is a MangedServices object that keeps track of the assistant.
- mNotificationAssistants = new NotificationAssistants();
+ mNotificationAssistants = notificationAssistants;
+
+ mPolicyFile = policyFile;
+ loadPolicyFile();
mStatusBar = getLocalService(StatusBarManagerInternal.class);
if (mStatusBar != null) {
timeoutFilter.addDataScheme(SCHEME_TIMEOUT);
getContext().registerReceiver(mNotificationTimeoutReceiver, timeoutFilter);
+ IntentFilter settingsRestoredFilter = new IntentFilter(Intent.ACTION_SETTING_RESTORED);
+ getContext().registerReceiver(mRestoreReceiver, settingsRestoredFilter);
+
mSettingsObserver = new SettingsObserver(mHandler);
mArchive = new Archive(resources.getInteger(
}
}, mUserProfiles);
+ final File systemDir = new File(Environment.getDataDirectory(), "system");
+
init(Looper.myLooper(), AppGlobals.getPackageManager(), getContext().getPackageManager(),
- getLocalService(LightsManager.class), new NotificationListeners(),
- null, snoozeHelper, new NotificationUsageStats(getContext()));
+ getLocalService(LightsManager.class),
+ new NotificationListeners(AppGlobals.getPackageManager()),
+ new NotificationAssistants(AppGlobals.getPackageManager()),
+ new ConditionProviders(getContext(), mUserProfiles, AppGlobals.getPackageManager()),
+ null, snoozeHelper, new NotificationUsageStats(getContext()),
+ new AtomicFile(new File(systemDir, "notification_policy.xml")));
publishBinderService(Context.NOTIFICATION_SERVICE, mService);
publishLocalService(NotificationManagerInternal.class, mInternalService);
}
cancelAllNotificationsInt(MY_UID, MY_PID, packageName, null, 0, 0, true,
UserHandle.getUserId(Binder.getCallingUid()), REASON_CHANNEL_BANNED, null);
+ final String[] packages = new String[] {packageName};
+ final int[] uids = new int[] {uid};
+
// Listener & assistant
- mListeners.onPackagesChanged(true, new String[] {packageName});
- mNotificationAssistants.onPackagesChanged(true, new String[] {packageName});
+ mListeners.onPackagesChanged(true, packages, uids);
+ mNotificationAssistants.onPackagesChanged(true, packages, uids);
// Zen
- mConditionProviders.onPackagesChanged(true, new String[] {packageName});
+ mConditionProviders.onPackagesChanged(true, packages, uids);
// Reset notification preferences
if (!fromApp) {
- mRankingHelper.onPackagesChanged(true, UserHandle.getCallingUserId(),
- new String[]{packageName}, new int[]{uid});
+ mRankingHelper.onPackagesChanged(
+ true, UserHandle.getCallingUserId(), packages, uids);
}
savePolicyFile();
String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
final int packageCount = packages.length;
for (int i = 0; i < packageCount; i++) {
- if (checkPolicyAccess(packages[i])) {
+ if (mConditionProviders.isPackageOrComponentAllowed(
+ packages[i], UserHandle.getUserId(uid))) {
accessAllowed = true;
}
}
}
private boolean checkPackagePolicyAccess(String pkg) {
- return mPolicyAccess.isPackageGranted(pkg);
+ return mConditionProviders.isPackageOrComponentAllowed(
+ pkg, getCallingUserHandle().getIdentifier());
}
private boolean checkPolicyAccess(String pkg) {
}
@Override
- public String[] getPackagesRequestingNotificationPolicyAccess()
- throws RemoteException {
- enforceSystemOrSystemUI("request policy access packages");
- final long identity = Binder.clearCallingIdentity();
- try {
- return mPolicyAccess.getRequestingPackages();
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @Override
public void setNotificationPolicyAccessGranted(String pkg, boolean granted)
throws RemoteException {
- enforceSystemOrSystemUI("grant notification policy access");
- final long identity = Binder.clearCallingIdentity();
- try {
- synchronized (mNotificationLock) {
- mPolicyAccess.put(pkg, granted);
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
+ checkCallerIsSystemOrShell();
+ mConditionProviders.setPackageOrComponentEnabled(
+ pkg, getCallingUserHandle().getIdentifier(), true, granted);
+
+ getContext().sendBroadcastAsUser(new Intent(NotificationManager
+ .ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
+ .setPackage(pkg)
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
+ getCallingUserHandle(), null);
+
+ savePolicyFile();
}
@Override
}
@Override
+ public List<String> getEnabledNotificationListenerPackages() {
+ checkCallerIsSystem();
+ return mListeners.getAllowedPackages(getCallingUserHandle().getIdentifier());
+ }
+
+ @Override
+ public List<ComponentName> getEnabledNotificationListeners(int userId) {
+ checkCallerIsSystem();
+ return mListeners.getAllowedComponents(userId);
+ }
+
+ @Override
+ public boolean isNotificationListenerAccessGranted(ComponentName listener) {
+ Preconditions.checkNotNull(listener);
+ checkCallerIsSystemOrSameApp(listener.getPackageName());
+ return mListeners.isPackageOrComponentAllowed(listener.flattenToString(),
+ getCallingUserHandle().getIdentifier());
+ }
+
+ @Override
+ public boolean isNotificationListenerAccessGrantedForUser(ComponentName listener,
+ int userId) {
+ Preconditions.checkNotNull(listener);
+ checkCallerIsSystem();
+ return mListeners.isPackageOrComponentAllowed(listener.flattenToString(),
+ userId);
+ }
+
+ @Override
+ public boolean isNotificationAssistantAccessGranted(ComponentName assistant) {
+ Preconditions.checkNotNull(assistant);
+ checkCallerIsSystemOrSameApp(assistant.getPackageName());
+ return mNotificationAssistants.isPackageOrComponentAllowed(assistant.flattenToString(),
+ getCallingUserHandle().getIdentifier());
+ }
+
+ @Override
+ public void setNotificationListenerAccessGranted(ComponentName listener,
+ boolean granted) throws RemoteException {
+ setNotificationListenerAccessGrantedForUser(
+ listener, getCallingUserHandle().getIdentifier(), granted);
+ }
+
+ @Override
+ public void setNotificationAssistantAccessGranted(ComponentName assistant,
+ boolean granted) throws RemoteException {
+ setNotificationAssistantAccessGrantedForUser(
+ assistant, getCallingUserHandle().getIdentifier(), granted);
+ }
+
+ @Override
+ public void setNotificationListenerAccessGrantedForUser(ComponentName listener, int userId,
+ boolean granted) throws RemoteException {
+ Preconditions.checkNotNull(listener);
+ enforceSystemOrSystemUI("grant notification listener access");
+ mConditionProviders.setPackageOrComponentEnabled(listener.flattenToString(),
+ userId, false, granted);
+ mListeners.setPackageOrComponentEnabled(listener.flattenToString(),
+ userId, true, granted);
+
+ getContext().sendBroadcastAsUser(new Intent(NotificationManager
+ .ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
+ .setPackage(listener.getPackageName())
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), getCallingUserHandle(), null);
+
+ savePolicyFile();
+ }
+
+ @Override
+ public void setNotificationAssistantAccessGrantedForUser(ComponentName assistant,
+ int userId, boolean granted) throws RemoteException {
+ Preconditions.checkNotNull(assistant);
+ enforceSystemOrSystemUI("grant notification assistant access");
+ mConditionProviders.setPackageOrComponentEnabled(assistant.flattenToString(),
+ userId, false, granted);
+ mNotificationAssistants.setPackageOrComponentEnabled(assistant.flattenToString(),
+ userId, true, granted);
+
+ getContext().sendBroadcastAsUser(new Intent(NotificationManager
+ .ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
+ .setPackage(assistant.getPackageName())
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), getCallingUserHandle(), null);
+
+ savePolicyFile();
+ }
+
+ @Override
public void applyEnqueuedAdjustmentFromAssistant(INotificationListener token,
Adjustment adjustment) throws RemoteException {
final long identity = Binder.clearCallingIdentity();
}
return uid;
}
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ShellCallback callback, ResultReceiver resultReceiver)
+ throws RemoteException {
+ new ShellCmd().exec(this, in, out, err, args, callback, resultReceiver);
+ }
};
private void applyAdjustment(NotificationRecord n, Adjustment adjustment) {
ZenLog.dump(pw, " ");
}
- pw.println("\n Policy access:");
- pw.print(" mPolicyAccess: "); pw.println(mPolicyAccess);
-
pw.println("\n Condition providers:");
mConditionProviders.dump(pw, filter);
return isUidSystemOrPhone(Binder.getCallingUid());
}
+ private void checkCallerIsSystemOrShell() {
+ if (Binder.getCallingUid() == Process.SHELL_UID) {
+ return;
+ }
+ checkCallerIsSystem();
+ }
+
private void checkCallerIsSystem() {
if (isCallerSystemOrPhone()) {
return;
}
public class NotificationAssistants extends ManagedServices {
+ static final String TAG_ENABLED_NOTIFICATION_ASSISTANTS = "enabled_assistants";
- public NotificationAssistants() {
- super(getContext(), mHandler, mNotificationLock, mUserProfiles);
+ public NotificationAssistants(IPackageManager pm) {
+ super(getContext(), mNotificationLock, mUserProfiles, pm);
}
@Override
Config c = new Config();
c.caption = "notification assistant service";
c.serviceInterface = NotificationAssistantService.SERVICE_INTERFACE;
+ c.managedServiceTypeTag = TAG_ENABLED_NOTIFICATION_ASSISTANTS;
c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT;
c.bindPermission = Manifest.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE;
c.settingsAction = Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS;
}
public class NotificationListeners extends ManagedServices {
+ static final String TAG_ENABLED_NOTIFICATION_LISTENERS = "enabled_listeners";
private final ArraySet<ManagedServiceInfo> mLightTrimListeners = new ArraySet<>();
- public NotificationListeners() {
- super(getContext(), mHandler, mNotificationLock, mUserProfiles);
+ public NotificationListeners(IPackageManager pm) {
+ super(getContext(), mNotificationLock, mUserProfiles, pm);
+
}
@Override
Config c = new Config();
c.caption = "notification listener";
c.serviceInterface = NotificationListenerService.SERVICE_INTERFACE;
+ c.managedServiceTypeTag = TAG_ENABLED_NOTIFICATION_LISTENERS;
c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_LISTENERS;
c.bindPermission = android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE;
c.settingsAction = Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS;
}
}
- private final class PolicyAccess {
- private static final String SEPARATOR = ":";
- private final String[] PERM = {
- android.Manifest.permission.ACCESS_NOTIFICATION_POLICY
- };
-
- public boolean isPackageGranted(String pkg) {
- return pkg != null && getGrantedPackages().contains(pkg);
- }
-
- public void put(String pkg, boolean granted) {
- if (pkg == null) return;
- final ArraySet<String> pkgs = getGrantedPackages();
- boolean changed;
- if (granted) {
- changed = pkgs.add(pkg);
- } else {
- changed = pkgs.remove(pkg);
- }
- if (!changed) return;
- final String setting = TextUtils.join(SEPARATOR, pkgs);
- final int currentUser = ActivityManager.getCurrentUser();
- Settings.Secure.putStringForUser(getContext().getContentResolver(),
- Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES,
- setting,
- currentUser);
- getContext().sendBroadcastAsUser(new Intent(NotificationManager
- .ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED)
- .setPackage(pkg)
- .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), new UserHandle(currentUser), null);
- }
+ private class ShellCmd extends ShellCommand {
+ public static final String USAGE = "help\n"
+ + "allow_dnd PACKAGE\n"
+ + "disallow_dnd PACKAGE";
- public ArraySet<String> getGrantedPackages() {
- final ArraySet<String> pkgs = new ArraySet<>();
-
- long identity = Binder.clearCallingIdentity();
+ @Override
+ public int onCommand(String cmd) {
try {
- final String setting = Settings.Secure.getStringForUser(
- getContext().getContentResolver(),
- Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES,
- ActivityManager.getCurrentUser());
- if (setting != null) {
- final String[] tokens = setting.split(SEPARATOR);
- for (int i = 0; i < tokens.length; i++) {
- String token = tokens[i];
- if (token != null) {
- token = token.trim();
- }
- if (TextUtils.isEmpty(token)) {
- continue;
- }
- pkgs.add(token);
+ switch (cmd) {
+ case "allow_dnd": {
+ getBinderService().setNotificationPolicyAccessGranted(
+ getNextArgRequired(), true);
+ }
+ break;
+
+ case "disallow_dnd": {
+ getBinderService().setNotificationPolicyAccessGranted(
+ getNextArgRequired(), false);
}
+ break;
+
+ default:
+ return handleDefaultCommands(cmd);
}
- } finally {
- Binder.restoreCallingIdentity(identity);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error running shell command", e);
}
- return pkgs;
+ return 0;
}
- public String[] getRequestingPackages() throws RemoteException {
- final ParceledListSlice list = mPackageManager
- .getPackagesHoldingPermissions(PERM, 0 /*flags*/,
- ActivityManager.getCurrentUser());
- final List<PackageInfo> pkgs = list.getList();
- if (pkgs == null || pkgs.isEmpty()) return new String[0];
- final int N = pkgs.size();
- final String[] rt = new String[N];
- for (int i = 0; i < N; i++) {
- rt[i] = pkgs.get(i).packageName;
- }
- return rt;
+ @Override
+ public void onHelp() {
+ getOutPrintWriter().println(USAGE);
}
}
}
}
if (type == XmlPullParser.START_TAG) {
if (TAG_PACKAGE.equals(tag)) {
- int uid = safeInt(parser, ATT_UID, Record.UNKNOWN_UID);
+ int uid = XmlUtils.safeInt(parser, ATT_UID, Record.UNKNOWN_UID);
String name = parser.getAttributeValue(null, ATT_NAME);
if (!TextUtils.isEmpty(name)) {
if (forRestore) {
}
Record r = getOrCreateRecord(name, uid,
- safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
- safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY),
- safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
- safeBool(parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE));
- r.importance = safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
- r.priority = safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY);
- r.visibility = safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
- r.showBadge = safeBool(parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
+ XmlUtils.safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE),
+ XmlUtils.safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY),
+ XmlUtils.safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY),
+ XmlUtils.safeBool(parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE));
+ r.importance = XmlUtils.safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
+ r.priority = XmlUtils.safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY);
+ r.visibility = XmlUtils.safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
+ r.showBadge = XmlUtils.safeBool(parser, ATT_SHOW_BADGE, DEFAULT_SHOW_BADGE);
final int innerDepth = parser.getDepth();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
if (TAG_CHANNEL.equals(tagName)) {
String id = parser.getAttributeValue(null, ATT_ID);
String channelName = parser.getAttributeValue(null, ATT_NAME);
- int channelImportance =
- safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
+ int channelImportance = XmlUtils.safeInt(
+ parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE);
if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
NotificationChannel channel = new NotificationChannel(id,
channelName, channelImportance);
return Collections.binarySearch(notificationList, target, mFinalComparator);
}
- private static boolean safeBool(XmlPullParser parser, String att, boolean defValue) {
- final String value = parser.getAttributeValue(null, att);
- if (TextUtils.isEmpty(value)) return defValue;
- return Boolean.parseBoolean(value);
- }
-
- private static int safeInt(XmlPullParser parser, String att, int defValue) {
- final String val = parser.getAttributeValue(null, att);
- return tryParseInt(val, defValue);
- }
-
- private static int tryParseInt(String value, int defValue) {
- if (TextUtils.isEmpty(value)) return defValue;
- try {
- return Integer.parseInt(value);
- } catch (NumberFormatException e) {
- return defValue;
- }
- }
-
/**
* Gets importance.
*/
--- /dev/null
+/**
+ * Copyright (c) 2017, 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 android.annotation.NonNull;
+import android.text.TextUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+
+class XmlUtils {
+
+ static @NonNull String safeString(XmlPullParser parser, String att, String defValue) {
+ final String value = parser.getAttributeValue(null, att);
+ if (value == null) return defValue;
+ return value;
+ }
+
+ static @NonNull boolean safeBool(XmlPullParser parser, String att, boolean defValue) {
+ final String value = parser.getAttributeValue(null, att);
+ if (TextUtils.isEmpty(value)) return defValue;
+ return Boolean.parseBoolean(value);
+ }
+
+ static @NonNull int safeInt(XmlPullParser parser, String att, int defValue) {
+ final String val = parser.getAttributeValue(null, att);
+ return tryParseInt(val, defValue);
+ }
+
+ private static int tryParseInt(String value, int defValue) {
+ if (TextUtils.isEmpty(value)) return defValue;
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ return defValue;
+ }
+ }
+
+}
import android.app.ActivityManagerInternal;
import android.app.ActivityManager;
import android.app.AppOpsManager;
+import android.app.INotificationManager;
import android.app.Vr2dDisplayProperties;
import android.app.NotificationManager;
import android.annotation.NonNull;
import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
+import java.util.List;
import java.util.Objects;
/**
private VrState mPendingState;
private final ArrayDeque<VrState> mLoggingDeque = new ArrayDeque<>(EVENT_LOG_SIZE);
private final NotificationAccessManager mNotifAccessManager = new NotificationAccessManager();
+ private INotificationManager mNotificationManager;
/** Tracks the state of the screen and keyguard UI.*/
private int mSystemSleepFlags = FLAG_AWAKE;
/**
@Override
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+ mNotificationManager = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
synchronized (mLock) {
Looper looper = Looper.getMainLooper();
Handler handler = new Handler(looper);
}
private void grantNotificationListenerAccess(String pkg, int userId) {
+ NotificationManager nm = mContext.getSystemService(NotificationManager.class);
PackageManager pm = mContext.getPackageManager();
ArraySet<ComponentName> possibleServices = EnabledComponentsObserver.loadComponentNames(pm,
userId, NotificationListenerService.SERVICE_INTERFACE,
android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE);
- ContentResolver resolver = mContext.getContentResolver();
-
- ArraySet<String> current = getNotificationListeners(resolver, userId);
for (ComponentName c : possibleServices) {
- String flatName = c.flattenToString();
- if (Objects.equals(c.getPackageName(), pkg)
- && !current.contains(flatName)) {
- current.add(flatName);
+ if (Objects.equals(c.getPackageName(), pkg)) {
+ nm.setNotificationListenerAccessGrantedForUser(c, userId, true);
}
}
-
- if (current.size() > 0) {
- String flatSettings = formatSettings(current);
- Settings.Secure.putStringForUser(resolver,
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
- flatSettings, userId);
- }
}
private void revokeNotificationListenerAccess(String pkg, int userId) {
- ContentResolver resolver = mContext.getContentResolver();
-
- ArraySet<String> current = getNotificationListeners(resolver, userId);
-
- ArrayList<String> toRemove = new ArrayList<>();
+ NotificationManager nm = mContext.getSystemService(NotificationManager.class);
+ List<ComponentName> current = nm.getEnabledNotificationListeners(userId);
- for (String c : current) {
- ComponentName component = ComponentName.unflattenFromString(c);
+ for (ComponentName component : current) {
if (component != null && component.getPackageName().equals(pkg)) {
- toRemove.add(c);
+ nm.setNotificationListenerAccessGrantedForUser(component, userId, false);
}
}
-
- current.removeAll(toRemove);
-
- String flatSettings = formatSettings(current);
- Settings.Secure.putStringForUser(resolver,
- Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
- flatSettings, userId);
}
private void grantCoarseLocationPermissionIfNeeded(String pkg, int userId) {
--- /dev/null
+/*
+ * Copyright (C) 2017 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 com.android.server.notification.ManagedServices.APPROVAL_BY_COMPONENT;
+import static com.android.server.notification.ManagedServices.APPROVAL_BY_PACKAGE;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import com.google.android.collect.Lists;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ManagedServicesTest extends NotificationTestCase {
+
+ @Mock
+ private IPackageManager mIpm;
+ @Mock
+ private PackageManager mPm;
+ @Mock
+ private UserManager mUm;
+ @Mock
+ private ManagedServices.UserProfiles mUserProfiles;
+ Object mLock = new Object();
+
+ UserInfo mZero = new UserInfo(0, "zero", 0);
+ UserInfo mTen = new UserInfo(10, "ten", 0);
+
+ private static final String SETTING = "setting";
+ private static final String SECONDARY_SETTING = "secondary_setting";
+
+ private ArrayMap<Integer, String> mExpectedPrimaryPackages;
+ private ArrayMap<Integer, String> mExpectedPrimaryComponentNames;
+ private ArrayMap<Integer, String> mExpectedSecondaryPackages;
+ private ArrayMap<Integer, String> mExpectedSecondaryComponentNames;
+
+ private ArrayMap<Integer, ArrayMap<Integer, String>> mExpectedPrimary = new ArrayMap<>();
+ private ArrayMap<Integer, ArrayMap<Integer, String>> mExpectedSecondary = new ArrayMap<>();
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ getContext().setMockPackageManager(mPm);
+ getContext().addMockSystemService(Context.USER_SERVICE, mUm);
+
+ List<UserInfo> users = new ArrayList<>();
+ users.add(mZero);
+ users.add(mTen);
+ users.add(new UserInfo(11, "11", 0));
+ users.add(new UserInfo(12, "12", 0));
+ for (UserInfo user : users) {
+ when(mUm.getUserInfo(eq(user.id))).thenReturn(user);
+ }
+ when(mUm.getUsers()).thenReturn(users);
+ when(mUserProfiles.getCurrentProfileIds()).thenReturn(new int[] {0, 10, 11, 12});
+
+ mExpectedPrimaryPackages = new ArrayMap<>();
+ mExpectedPrimaryPackages.put(0, "this.is.a.package.name:another.package");
+ mExpectedPrimaryPackages.put(10, "this.is.another.package");
+ mExpectedPrimaryPackages.put(11, "");
+ mExpectedPrimaryPackages.put(12, "bananas!");
+ mExpectedPrimaryComponentNames = new ArrayMap<>();
+ mExpectedPrimaryComponentNames.put(0, "this.is.a.package.name/Ba:another.package/B1");
+ mExpectedPrimaryComponentNames.put(10, "this.is.another.package/M1");
+ mExpectedPrimaryComponentNames.put(11, "");
+ mExpectedPrimaryComponentNames.put(12, "bananas!/Bananas!");
+ mExpectedPrimary.put(APPROVAL_BY_PACKAGE, mExpectedPrimaryPackages);
+ mExpectedPrimary.put(APPROVAL_BY_COMPONENT, mExpectedPrimaryComponentNames);
+
+ mExpectedSecondaryComponentNames = new ArrayMap<>();
+ mExpectedSecondaryComponentNames.put(0, "secondary/component.Name");
+ mExpectedSecondaryComponentNames.put(10,
+ "this.is.another.package/with.Component:component/2:package/component2");
+ mExpectedSecondaryPackages = new ArrayMap<>();
+ mExpectedSecondaryPackages.put(0, "secondary");
+ mExpectedSecondaryPackages.put(10,
+ "this.is.another.package:component:package");
+ mExpectedSecondary.put(APPROVAL_BY_PACKAGE, mExpectedSecondaryPackages);
+ mExpectedSecondary.put(APPROVAL_BY_COMPONENT, mExpectedSecondaryComponentNames);
+ }
+
+ @Test
+ public void testBackupAndRestore_migration() throws Exception {
+ for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+ mIpm, approvalLevel);
+
+ for (int userId : mExpectedPrimary.get(approvalLevel).keySet()) {
+ service.onSettingRestored(
+ service.getConfig().secureSettingName,
+ mExpectedPrimary.get(approvalLevel).get(userId),
+ userId);
+ }
+ verifyExpectedApprovedEntries(service, true);
+
+ for (int userId : mExpectedSecondary.get(approvalLevel).keySet()) {
+ service.onSettingRestored(service.getConfig().secondarySettingName,
+ mExpectedSecondary.get(approvalLevel).get(userId), userId);
+ }
+ verifyExpectedApprovedEntries(service);
+ }
+ }
+
+ @Test
+ public void testReadXml_migrationFromSettings() throws Exception {
+ for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+ mIpm, approvalLevel);
+
+ // approved services aren't in xml
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(new byte[]{})),
+ null);
+ writeExpectedValuesToSettings(approvalLevel);
+
+ assertTrue(service.readXml(parser));
+
+ verifyExpectedApprovedEntries(service);
+ }
+ }
+
+ @Test
+ public void testReadXml() throws Exception {
+ for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+ mIpm, approvalLevel);
+
+ assertFalse(loadXml(service));
+
+ verifyExpectedApprovedEntries(service);
+ }
+ }
+
+ @Test
+ public void testWriteXml_trimsMissingServices() throws Exception {
+ for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+ mIpm, approvalLevel);
+ loadXml(service);
+
+ // remove missing
+ mExpectedPrimaryPackages.put(0, "another.package");
+ mExpectedPrimaryPackages.remove(12);
+ mExpectedPrimaryComponentNames.put(0, "another.package/B1");
+ mExpectedPrimaryComponentNames.remove(12);
+ mExpectedSecondaryPackages.put(10, "this.is.another.package:component");
+ mExpectedSecondaryComponentNames.put(
+ 10, "this.is.another.package/with.Component:component/2");
+
+ for (UserInfo userInfo : mUm.getUsers()) {
+ List<String> entriesExpectedToHaveServices = new ArrayList<>();
+ if (mExpectedPrimary.get(approvalLevel).containsKey(userInfo.id)) {
+ for (String packageOrComponent :
+ mExpectedPrimary.get(approvalLevel).get(userInfo.id).split(":")) {
+ if (!TextUtils.isEmpty(packageOrComponent)) {
+ entriesExpectedToHaveServices.add(
+ service.getPackageName(packageOrComponent));
+ }
+ }
+ }
+ if (mExpectedSecondary.get(approvalLevel).containsKey(userInfo.id)) {
+ for (String packageOrComponent :
+ mExpectedSecondary.get(approvalLevel).get(userInfo.id).split(":")) {
+ if (!TextUtils.isEmpty(packageOrComponent)) {
+ entriesExpectedToHaveServices.add(
+ service.getPackageName(packageOrComponent));
+ }
+ }
+ }
+ addExpectedServices(service, entriesExpectedToHaveServices, userInfo.id);
+ }
+
+ XmlSerializer serializer = new FastXmlSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+ service.writeXml(serializer, true);
+ serializer.endDocument();
+ serializer.flush();
+
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(baos.toByteArray())), null);
+ parser.nextTag();
+ assertFalse(service.readXml(parser));
+
+ verifyExpectedApprovedEntries(service);
+ assertFalse(service.isPackageOrComponentAllowed("this.is.a.package.name", 0));
+ assertFalse(service.isPackageOrComponentAllowed("bananas!", 12));
+ assertFalse(service.isPackageOrComponentAllowed("package/component2", 10));
+ }
+ }
+
+ @Test
+ public void testWriteXml_writesSetting() throws Exception {
+ for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+ mIpm, approvalLevel);
+ loadXml(service);
+
+ XmlSerializer serializer = new FastXmlSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+ service.writeXml(serializer, false);
+ serializer.endDocument();
+ serializer.flush();
+
+ for (int userId : mUserProfiles.getCurrentProfileIds()) {
+ List<String> expected =
+ stringToList(mExpectedPrimary.get(approvalLevel).get(userId));
+ List<String> actual = stringToList(Settings.Secure.getStringForUser(
+ getContext().getContentResolver(),
+ service.getConfig().secureSettingName, userId));
+ assertContentsInAnyOrder(actual, expected);
+ }
+ }
+ }
+
+ @Test
+ public void rebindServices_onlyBindsExactMatchesIfComponent() throws Exception {
+ // If the primary and secondary lists contain component names, only those components within
+ // the package should be matched
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+ mIpm,
+ ManagedServices.APPROVAL_BY_COMPONENT);
+
+ List<String> packages = new ArrayList<>();
+ packages.add("package");
+ packages.add("anotherPackage");
+ addExpectedServices(service, packages, 0);
+
+ // only 2 components are approved per package
+ mExpectedPrimaryComponentNames.clear();
+ mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2");
+ mExpectedSecondaryComponentNames.clear();
+ mExpectedSecondaryComponentNames.put(0, "anotherPackage/C1:anotherPackage/C2");
+
+ loadXml(service);
+
+ // verify the 2 components per package are enabled (bound)
+ verifyExpectedBoundEntries(service, true);
+ verifyExpectedBoundEntries(service, false);
+
+ // verify the last component per package is not enabled/we don't try to bind to it
+ for (String pkg : packages) {
+ ComponentName unapprovedAdditionalComponent =
+ ComponentName.unflattenFromString(pkg + "/C3");
+ assertFalse(
+ service.isComponentEnabledForCurrentProfiles(
+ unapprovedAdditionalComponent));
+ verify(mIpm, never()).getServiceInfo(
+ eq(unapprovedAdditionalComponent), anyInt(), anyInt());
+ }
+ }
+
+ @Test
+ public void rebindServices_bindsEverythingInAPackage() throws Exception {
+ // If the primary and secondary lists contain packages, all components within those packages
+ // should be bound
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_PACKAGE);
+
+ List<String> packages = new ArrayList<>();
+ packages.add("package");
+ packages.add("packagea");
+ addExpectedServices(service, packages, 0);
+
+ // 2 approved packages
+ mExpectedPrimaryPackages.clear();
+ mExpectedPrimaryPackages.put(0, "package");
+ mExpectedSecondaryPackages.clear();
+ mExpectedSecondaryPackages.put(0, "packagea");
+
+ loadXml(service);
+
+ // verify the 3 components per package are enabled (bound)
+ verifyExpectedBoundEntries(service, true);
+ verifyExpectedBoundEntries(service, false);
+ }
+
+ @Test
+ public void testPackageUninstall_packageNoLongerInApprovedList() throws Exception {
+ for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+ mIpm, approvalLevel);
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(new byte[]{})),
+ null);
+ writeExpectedValuesToSettings(approvalLevel);
+ service.readXml(parser);
+
+ mExpectedPrimaryPackages.put(0, "another.package");
+ mExpectedPrimaryComponentNames.put(0, "another.package/B1");
+ service.onPackagesChanged(true, new String[]{"this.is.a.package.name"}, new int[]{103});
+
+ verifyExpectedApprovedEntries(service);
+ }
+ }
+
+ @Test
+ public void testPackageUninstall_componentNoLongerInApprovedList() throws Exception {
+ for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+ mIpm, approvalLevel);
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(new ByteArrayInputStream(new byte[]{})),
+ null);
+ writeExpectedValuesToSettings(approvalLevel);
+ service.readXml(parser);
+
+ mExpectedSecondaryComponentNames.put(10, "component/2");
+ mExpectedSecondaryPackages.put(10, "component");
+ service.onPackagesChanged(true, new String[]{"this.is.another.package"}, new int[]{
+ UserHandle.PER_USER_RANGE + 1});
+
+ verifyExpectedApprovedEntries(service);
+ }
+ }
+
+ @Test
+ public void testSetPackageOrComponentEnabled() throws Exception {
+ for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+ mIpm, approvalLevel);
+ ArrayMap<Integer, ArrayList<String>> expectedEnabled = new ArrayMap<>();
+ expectedEnabled.put(0,
+ Lists.newArrayList(new String[]{"package/Comp", "package/C2", "again/M4"}));
+ expectedEnabled.put(10,
+ Lists.newArrayList(new String[]{"user10package/B", "user10/Component",
+ "user10package1/K", "user10.3/Component", "user10package2/L",
+ "user10.4/Component"}));
+
+ for (int userId : expectedEnabled.keySet()) {
+ ArrayList<String> expectedForUser = expectedEnabled.get(userId);
+ for (int i = 0; i < expectedForUser.size(); i++) {
+ boolean primary = i % 2 == 0;
+ service.setPackageOrComponentEnabled(expectedForUser.get(i), userId, primary,
+ true);
+ }
+ }
+
+ // verify everything added is approved
+ for (int userId : expectedEnabled.keySet()) {
+ ArrayList<String> expectedForUser = expectedEnabled.get(userId);
+ for (int i = 0; i < expectedForUser.size(); i++) {
+ String verifyValue = (approvalLevel == APPROVAL_BY_COMPONENT)
+ ? expectedForUser.get(i)
+ : service.getPackageName(expectedForUser.get(i));
+ assertTrue("Not allowed: user: " + userId + " entry: " + verifyValue
+ + " for approval level " + approvalLevel,
+ service.isPackageOrComponentAllowed(verifyValue, userId));
+ }
+ }
+
+ ArrayMap<Integer, ArrayList<String>> expectedNoAccess = new ArrayMap<>();
+ for (int userId : expectedEnabled.keySet()) {
+ ArrayList<String> expectedForUser = expectedEnabled.get(userId);
+ for (int i = expectedForUser.size() - 1; i >= 0; i--) {
+ ArrayList<String> removed = new ArrayList<>();
+ if (i % 3 == 0) {
+ String revokeAccessFor = expectedForUser.remove(i);
+ removed.add(revokeAccessFor);
+ service.setPackageOrComponentEnabled(
+ revokeAccessFor, userId, i % 2 == 0, false);
+ }
+ expectedNoAccess.put(userId, removed);
+ }
+ }
+
+ // verify everything still there is approved
+ for (int userId : expectedEnabled.keySet()) {
+ ArrayList<String> expectedForUser = expectedEnabled.get(userId);
+ for (int i = 0; i < expectedForUser.size(); i++) {
+ String verifyValue = (approvalLevel == APPROVAL_BY_COMPONENT)
+ ? expectedForUser.get(i)
+ : service.getPackageName(expectedForUser.get(i));
+ assertTrue("Not allowed: user: " + userId + " entry: " + verifyValue,
+ service.isPackageOrComponentAllowed(verifyValue, userId));
+ }
+ }
+ // verify everything removed isn't
+ for (int userId : expectedNoAccess.keySet()) {
+ ArrayList<String> notExpectedForUser = expectedNoAccess.get(userId);
+ for (int i = 0; i < notExpectedForUser.size(); i++) {
+ assertFalse(
+ "Is allowed: user: " + userId + " entry: " + notExpectedForUser.get(i),
+ service.isPackageOrComponentAllowed(notExpectedForUser.get(i), userId));
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testGetAllowedPackages() throws Exception {
+ for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
+ mIpm, approvalLevel);
+ loadXml(service);
+
+ List<String> allowedPackagesForUser0 = new ArrayList<>();
+ allowedPackagesForUser0.add("this.is.a.package.name");
+ allowedPackagesForUser0.add("another.package");
+ allowedPackagesForUser0.add("secondary");
+
+ List<String> actual = service.getAllowedPackages(0);
+ assertEquals(3, actual.size());
+ for (String pkg : allowedPackagesForUser0) {
+ assertTrue(actual.contains(pkg));
+ }
+
+ List<String> allowedPackagesForUser10 = new ArrayList<>();
+ allowedPackagesForUser10.add("this.is.another.package");
+ allowedPackagesForUser10.add("package");
+ allowedPackagesForUser10.add("this.is.another.package");
+ allowedPackagesForUser10.add("component");
+
+ actual = service.getAllowedPackages(10);
+ assertEquals(4, actual.size());
+ for (String pkg : allowedPackagesForUser10) {
+ assertTrue(actual.contains(pkg));
+ }
+ }
+ }
+
+ @Test
+ public void testGetAllowedComponents() throws Exception {
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_COMPONENT);
+ loadXml(service);
+
+ List<ComponentName> expected = new ArrayList<>();
+ expected.add(ComponentName.unflattenFromString("this.is.another.package/M1"));
+ expected.add(ComponentName.unflattenFromString("this.is.another.package/with.Component"));
+ expected.add(ComponentName.unflattenFromString("component/2"));
+ expected.add(ComponentName.unflattenFromString("package/component2"));
+
+ List<ComponentName> actual = service.getAllowedComponents(10);
+
+ assertContentsInAnyOrder(expected, actual);
+
+ assertEquals(expected.size(), actual.size());
+
+ for (ComponentName cn : expected) {
+ assertTrue("Actual missing " + cn, actual.contains(cn));
+ }
+
+ for (ComponentName cn : actual) {
+ assertTrue("Actual contains extra " + cn, expected.contains(cn));
+ }
+ }
+
+ @Test
+ public void testGetAllowedComponents_approvalByPackage() throws Exception {
+ ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_PACKAGE);
+ loadXml(service);
+
+ assertEquals(0, service.getAllowedComponents(10).size());
+ }
+
+ private boolean loadXml(ManagedServices service) throws Exception {
+ final StringBuffer xml = new StringBuffer();
+ xml.append("<" + service.getConfig().managedServiceTypeTag + ">\n");
+ for (int userId : mExpectedPrimary.get(service.mApprovalLevel).keySet()) {
+ xml.append(getXmlEntry(
+ mExpectedPrimary.get(service.mApprovalLevel).get(userId), userId, true));
+ }
+ for (int userId : mExpectedSecondary.get(service.mApprovalLevel).keySet()) {
+ xml.append(getXmlEntry(
+ mExpectedSecondary.get(service.mApprovalLevel).get(userId), userId, false));
+ }
+ xml.append("</" + service.getConfig().managedServiceTypeTag + ">");
+
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(xml.toString().getBytes())), null);
+ parser.nextTag();
+ return service.readXml(parser);
+ }
+
+ private void addExpectedServices(final ManagedServices service, final List<String> packages,
+ int userId) {
+ when(mPm.queryIntentServicesAsUser(any(), anyInt(), eq(userId))).
+ thenAnswer(new Answer<List<ResolveInfo>>() {
+ @Override
+ public List<ResolveInfo> answer(InvocationOnMock invocationOnMock)
+ throws Throwable {
+ Object[] args = invocationOnMock.getArguments();
+ Intent invocationIntent = (Intent) args[0];
+ if (invocationIntent != null) {
+ if (invocationIntent.getAction().equals(
+ service.getConfig().serviceInterface)
+ && packages.contains(invocationIntent.getPackage())) {
+ List<ResolveInfo> dummyServices = new ArrayList<>();
+ for (int i = 1; i <= 3; i ++) {
+ ResolveInfo resolveInfo = new ResolveInfo();
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.packageName = invocationIntent.getPackage();
+ serviceInfo.name = "C"+i;
+ serviceInfo.permission = service.getConfig().bindPermission;
+ resolveInfo.serviceInfo = serviceInfo;
+ dummyServices.add(resolveInfo);
+ }
+ return dummyServices;
+ }
+ }
+ return new ArrayList<>();
+ }
+ });
+ }
+
+ private List<String> stringToList(String list) {
+ if (list == null) {
+ list = "";
+ }
+ return new ArrayList<>(Lists.newArrayList(list.split(
+ ManagedServices.ENABLED_SERVICES_SEPARATOR)));
+ }
+
+ private void assertContentsInAnyOrder(List<?> expected, List<?> actual) {
+ assertEquals(expected.size(), actual.size());
+
+ for (Object o : expected) {
+ assertTrue("Actual missing " + o, actual.contains(o));
+ }
+
+ for (Object o : actual) {
+ assertTrue("Actual contains extra " + o, expected.contains(o));
+ }
+ }
+
+ private void verifyExpectedBoundEntries(ManagedServices service, boolean primary)
+ throws Exception {
+ ArrayMap<Integer, String> verifyMap = primary ? mExpectedPrimary.get(service.mApprovalLevel)
+ : mExpectedSecondary.get(service.mApprovalLevel);
+ for (int userId : verifyMap.keySet()) {
+ for (String packageOrComponent : verifyMap.get(userId).split(":")) {
+ if (!TextUtils.isEmpty(packageOrComponent)) {
+ if (service.mApprovalLevel == APPROVAL_BY_PACKAGE) {
+ assertTrue(packageOrComponent, service.isComponentEnabledForPackage(packageOrComponent));
+ for (int i = 1; i <= 3; i ++) {
+ ComponentName componentName = ComponentName.unflattenFromString(
+ packageOrComponent +"/C" + i);
+ assertTrue(service.isComponentEnabledForCurrentProfiles(
+ componentName));
+ verify(mIpm, times(1)).getServiceInfo(
+ eq(componentName), anyInt(), anyInt());
+ }
+ } else {
+ ComponentName componentName =
+ ComponentName.unflattenFromString(packageOrComponent);
+ assertTrue(service.isComponentEnabledForCurrentProfiles(componentName));
+ verify(mIpm, times(1)).getServiceInfo(
+ eq(componentName), anyInt(), anyInt());
+ }
+ }
+ }
+ }
+ }
+
+ private void verifyExpectedApprovedEntries(ManagedServices service) {
+ verifyExpectedApprovedEntries(service, true);
+ verifyExpectedApprovedEntries(service, false);
+ }
+
+ private void verifyExpectedApprovedEntries(ManagedServices service, boolean primary) {
+ ArrayMap<Integer, String> verifyMap = primary
+ ? mExpectedPrimary.get(service.mApprovalLevel)
+ : mExpectedSecondary.get(service.mApprovalLevel);
+ for (int userId : verifyMap.keySet()) {
+ for (String verifyValue : verifyMap.get(userId).split(":")) {
+ if (!TextUtils.isEmpty(verifyValue)) {
+ assertTrue("service type " + service.mApprovalLevel + ":"
+ + verifyValue + " is not allowed for user " + userId,
+ service.isPackageOrComponentAllowed(verifyValue, userId));
+ }
+ }
+ }
+ }
+
+ private boolean isPackage(String packageOrComponent) {
+ final ComponentName component = ComponentName.unflattenFromString(packageOrComponent);
+ if (component != null) {
+ return false;
+ }
+ return true;
+ }
+
+ private void writeExpectedValuesToSettings(int approvalLevel) {
+ for (int userId : mExpectedPrimary.get(approvalLevel).keySet()) {
+ Settings.Secure.putStringForUser(getContext().getContentResolver(), SETTING,
+ mExpectedPrimary.get(approvalLevel).get(userId), userId);
+ }
+ for (int userId : mExpectedSecondary.get(approvalLevel).keySet()) {
+ Settings.Secure.putStringForUser(getContext().getContentResolver(), SECONDARY_SETTING,
+ mExpectedSecondary.get(approvalLevel).get(userId), userId);
+ }
+ }
+
+ private String getXmlEntry(String approved, int userId, boolean isPrimary) {
+ return "<" + ManagedServices.TAG_MANAGED_SERVICES + " "
+ + ManagedServices.ATT_USER_ID + "=\"" + userId +"\" "
+ + ManagedServices.ATT_IS_PRIMARY + "=\"" + isPrimary +"\" "
+ + ManagedServices.ATT_APPROVED_LIST + "=\"" + approved +"\" "
+ + "/>\n";
+ }
+
+ class TestManagedServices extends ManagedServices {
+
+ public TestManagedServices(Context context, Object mutex, UserProfiles userProfiles,
+ IPackageManager pm, int approvedServiceType) {
+ super(context, mutex, userProfiles, pm);
+ mApprovalLevel = approvedServiceType;
+ }
+
+ @Override
+ protected Config getConfig() {
+ final Config c = new Config();
+ c.managedServiceTypeTag= "test";
+ c.secureSettingName = SETTING;
+ c.secondarySettingName = SECONDARY_SETTING;
+ c.bindPermission = "permission";
+ c.serviceInterface = "serviceInterface";
+ return c;
+ }
+
+ @Override
+ protected IInterface asInterface(IBinder binder) {
+ return null;
+ }
+
+ @Override
+ protected boolean checkType(IInterface service) {
+ return false;
+ }
+
+ @Override
+ protected void onServiceAdded(ManagedServiceInfo info) {
+
+ }
+ }
+}
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
+import android.util.AtomicFile;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@Mock
private RankingHelper mRankingHelper;
@Mock
+ AtomicFile mPolicyFile;
+ File mFile;
+ @Mock
private NotificationUsageStats mUsageStats;
private NotificationChannel mTestNotificationChannel = new NotificationChannel(
TEST_CHANNEL_ID, TEST_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
@Mock
private NotificationManagerService.NotificationListeners mNotificationListeners;
+ @Mock private NotificationManagerService.NotificationAssistants mNotificationAssistants;
+ @Mock private ConditionProviders mConditionProviders;
private ManagedServices.ManagedServiceInfo mListener;
@Mock private ICompanionDeviceManager mCompanionMgr;
@Mock SnoozeHelper mSnoozeHelper;
// Use this testable looper.
mTestableLooper = TestableLooper.get(this);
+ mFile = new File(mContext.getCacheDir(), "test.xml");
+ mFile.createNewFile();
+ when(mPolicyFile.openRead()).thenReturn(new FileInputStream(mFile));
+ when(mPolicyFile.startWrite()).thenReturn(new FileOutputStream(mFile));
+
mListener = mNotificationListeners.new ManagedServiceInfo(
null, new ComponentName(PKG, "test_class"), uid, true, null, 0);
when(mNotificationListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
- mNotificationManagerService.init(mTestableLooper.getLooper(), mPackageManager,
- mPackageManagerClient, mockLightsManager, mNotificationListeners, mCompanionMgr,
- mSnoozeHelper, mUsageStats);
+ try {
+ mNotificationManagerService.init(mTestableLooper.getLooper(), mPackageManager,
+ mPackageManagerClient, mockLightsManager, mNotificationListeners,
+ mNotificationAssistants, mConditionProviders, mCompanionMgr,
+ mSnoozeHelper, mUsageStats, mPolicyFile);
+ } catch (SecurityException e) {
+ if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+ throw e;
+ }
+ }
// Tests call directly into the Binder.
mBinderService = mNotificationManagerService.getBinderService();
PKG, new ParceledListSlice(Arrays.asList(mTestNotificationChannel)));
}
+ @After
+ public void tearDown() throws Exception {
+ mFile.delete();
+ }
+
public void waitForIdle() throws Exception {
mTestableLooper.processAllMessages();
}
verify(mSnoozeHelper, never()).repostGroupSummary(anyString(), anyInt(), anyString());
}
+
+ @Test
+ public void testSetListenerAccess() throws Exception {
+ ComponentName c = ComponentName.unflattenFromString("package/Component");
+ try {
+ mBinderService.setNotificationListenerAccessGranted(c, true);
+ } catch (SecurityException e) {
+ if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+ throw e;
+ }
+ }
+
+ verify(mNotificationListeners, times(1)).setPackageOrComponentEnabled(
+ c.flattenToString(), 0, true, true);
+ verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
+ c.flattenToString(), 0, false, true);
+ verify(mNotificationAssistants, never()).setPackageOrComponentEnabled(
+ any(), anyInt(), anyBoolean(), anyBoolean());
+ }
+
+ @Test
+ public void testSetAssistantAccess() throws Exception {
+ ComponentName c = ComponentName.unflattenFromString("package/Component");
+ try {
+ mBinderService.setNotificationAssistantAccessGranted(c, true);
+ } catch (SecurityException e) {
+ if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+ throw e;
+ }
+ }
+
+ verify(mNotificationAssistants, times(1)).setPackageOrComponentEnabled(
+ c.flattenToString(), 0, true, true);
+ verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
+ c.flattenToString(), 0, false, true);
+ verify(mNotificationListeners, never()).setPackageOrComponentEnabled(
+ any(), anyInt(), anyBoolean(), anyBoolean());
+ }
+
+ @Test
+ public void testSetDndAccess() throws Exception {
+ ComponentName c = ComponentName.unflattenFromString("package/Component");
+ try {
+ mBinderService.setNotificationPolicyAccessGranted(c.getPackageName(), true);
+ } catch (SecurityException e) {
+ if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+ throw e;
+ }
+ }
+
+ verify(mConditionProviders, times(1)).setPackageOrComponentEnabled(
+ c.getPackageName(), 0, true, true);
+ verify(mNotificationAssistants, never()).setPackageOrComponentEnabled(
+ any(), anyInt(), anyBoolean(), anyBoolean());
+ verify(mNotificationListeners, never()).setPackageOrComponentEnabled(
+ any(), anyInt(), anyBoolean(), anyBoolean());
+ }
}
public final TestableContext mContext =
new TestableContext(InstrumentationRegistry.getContext(), null);
- protected Context getContext() {
+ protected TestableContext getContext() {
return mContext;
}
}
public class TestableSettingsProvider extends MockContentProvider {
private static final String TAG = "TestableSettingsProvider";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true;
private static final String MY_UNIQUE_KEY = "Key_" + TestableSettingsProvider.class.getName();
private static TestableSettingsProvider sInstance;