diff options
19 files changed, 1605 insertions, 593 deletions
diff --git a/api/current.txt b/api/current.txt index 52005cb84608..c1b025ad06cb 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5592,6 +5592,7 @@ package android.app { 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); diff --git a/api/system-current.txt b/api/system-current.txt index c1c1a0f6883f..83b5baea8136 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5798,6 +5798,7 @@ package android.app { 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); diff --git a/api/test-current.txt b/api/test-current.txt index 169bea0ddbe4..debb106280b4 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -5606,6 +5606,7 @@ package android.app { 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); @@ -35028,7 +35029,7 @@ package android.provider { 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"; diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 1c1883b3b7f5..08821bebd57e 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -116,6 +116,16 @@ interface INotificationManager 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); @@ -123,7 +133,6 @@ interface INotificationManager 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); diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 235b8d445b1c..a5191258f598 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -18,14 +18,12 @@ package android.app; 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; @@ -33,7 +31,6 @@ import android.os.Build; 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; @@ -41,10 +38,8 @@ import android.os.ServiceManager; 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; @@ -747,14 +742,14 @@ public class NotificationManager { } /** - * 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 @@ -769,6 +764,39 @@ public class NotificationManager { } } + /** + * 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(); @@ -780,6 +808,18 @@ public class NotificationManager { } /** + * @hide + */ + public List<String> getEnabledNotificationListenerPackages() { + INotificationManager service = getService(); + try { + return service.getEnabledNotificationListenerPackages(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Gets the current notification policy. * * <p> @@ -825,21 +865,34 @@ public class NotificationManager { } /** @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; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index ed2b0b2e3e33..f5b5006201e1 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -31,6 +31,7 @@ import android.app.ActivityThread; 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; @@ -6607,28 +6608,36 @@ public final class Settings { 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"; @@ -7049,7 +7058,6 @@ public final class Settings { AUTOFILL_SERVICE, ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, ENABLED_ACCESSIBILITY_SERVICES, - ENABLED_NOTIFICATION_LISTENERS, ENABLED_VR_LISTENERS, ENABLED_INPUT_METHODS, TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, @@ -7128,6 +7136,9 @@ public final class Settings { /** @hide */ public static final String[] LEGACY_RESTORE_SETTINGS = { + ENABLED_NOTIFICATION_LISTENERS, + ENABLED_NOTIFICATION_ASSISTANT, + ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES }; /** diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 7dc72db7cd4c..53ba9f7bcf66 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -17,24 +17,25 @@ 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 @@ -428,6 +429,7 @@ public class SettingsBackupTest { 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, diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 1df2c807f64f..1bd58ad85424 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -3166,33 +3166,7 @@ public class SettingsProvider extends ContentProvider { } 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; } @@ -3365,58 +3339,7 @@ public class SettingsProvider extends ContentProvider { } 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; } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 38c615791d9f..67920edcf56a 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -16,10 +16,8 @@ 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; @@ -57,13 +55,11 @@ import android.os.Process; 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; @@ -80,7 +76,6 @@ import com.android.server.Watchdog.Monitor; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; /** @@ -112,6 +107,7 @@ public class MediaSessionService extends SystemService implements Monitor { 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. @@ -129,6 +125,8 @@ public class MediaSessionService extends SystemService implements Monitor { 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 @@ -471,28 +469,11 @@ public class MediaSessionService extends SystemService implements Monitor { 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; diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java index c28fb67d6c2d..5cc14b5ed520 100644 --- a/services/core/java/com/android/server/notification/ConditionProviders.java +++ b/services/core/java/com/android/server/notification/ConditionProviders.java @@ -16,14 +16,12 @@ 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; @@ -32,7 +30,6 @@ import android.provider.Settings; 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; @@ -45,6 +42,9 @@ import java.util.ArrayList; 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 @@ -52,11 +52,12 @@ public class ConditionProviders extends ManagedServices { 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) { @@ -83,6 +84,7 @@ public class ConditionProviders extends ManagedServices { 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; @@ -164,7 +166,7 @@ public class ConditionProviders extends ManagedServices { } @Override - public void onPackagesChanged(boolean removingPackage, String[] pkgList) { + public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uid) { if (removingPackage) { INotificationManager inm = NotificationManager.getService(); @@ -179,7 +181,7 @@ public class ConditionProviders extends ManagedServices { } } } - super.onPackagesChanged(removingPackage, pkgList); + super.onPackagesChanged(removingPackage, pkgList, uid); } public ManagedServiceInfo checkServiceToken(IConditionProvider provider) { @@ -277,31 +279,6 @@ public class ConditionProviders extends ManagedServices { } } - @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*/); diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 73a365b0da72..2f88740cd01b 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -25,12 +25,10 @@ import android.annotation.NonNull; 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; @@ -39,19 +37,16 @@ import android.content.pm.PackageManager.NameNotFoundException; 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; @@ -59,13 +54,18 @@ import android.util.SparseArray; 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. @@ -83,66 +83,60 @@ abstract public class ManagedServices { 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(); @@ -167,17 +161,33 @@ abstract public class ManagedServices { 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) { @@ -201,67 +211,240 @@ abstract public class ManagedServices { } } - // 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; @@ -271,11 +454,10 @@ abstract public class ManagedServices { 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; } @@ -288,7 +470,7 @@ abstract public class ManagedServices { return null; } - public ManagedServiceInfo checkServiceTokenLocked(IInterface service) { + protected ManagedServiceInfo checkServiceTokenLocked(IInterface service) { checkNotNull(service); ManagedServiceInfo info = getServiceFromTokenLocked(service); if (info != null) { @@ -315,9 +497,9 @@ abstract public class ManagedServices { /** * 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(); @@ -327,7 +509,7 @@ abstract public class ManagedServices { } } - public void setComponentState(ComponentName component, boolean enabled) { + protected void setComponentState(ComponentName component, boolean enabled) { boolean previous = !mSnoozingForCurrentProfiles.contains(component); if (previous == enabled) { return; @@ -345,7 +527,6 @@ abstract public class ManagedServices { component.flattenToShortString()); } - synchronized (mMutex) { final int[] userIds = mUserProfiles.getCurrentProfileIds(); @@ -359,84 +540,31 @@ abstract public class ManagedServices { } } - 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); @@ -445,7 +573,7 @@ abstract public class ManagedServices { } 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); @@ -468,50 +596,73 @@ abstract public class ManagedServices { 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; } /** @@ -526,11 +677,19 @@ abstract public class ManagedServices { 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)); + } } } @@ -552,7 +711,7 @@ abstract public class ManagedServices { // 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; } @@ -595,7 +754,7 @@ abstract public class ManagedServices { 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; } @@ -719,7 +878,6 @@ abstract public class ManagedServices { } } catch (SecurityException ex) { Slog.e(TAG, "Unable to bind " + getCaption() + " service: " + intent, ex); - return; } } @@ -816,45 +974,6 @@ abstract public class ManagedServices { } } - 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; @@ -1000,6 +1119,7 @@ abstract public class ManagedServices { public String serviceInterface; public String secureSettingName; public String secondarySettingName; + public String managedServiceTypeTag; public String bindPermission; public String settingsAction; public int clientLabel; diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 5ee7ac4d16d1..e7bfa2d57a2b 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -87,7 +87,6 @@ import android.content.Intent; 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; @@ -110,7 +109,10 @@ import android.os.Looper; 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; @@ -195,9 +197,9 @@ import java.util.ArrayList; 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} */ @@ -319,7 +321,6 @@ public class NotificationManagerService extends SystemService { 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<>(); @@ -404,14 +405,58 @@ public class NotificationManagerService extends SystemService { } + 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(); } } @@ -425,6 +470,8 @@ public class NotificationManagerService extends SystemService { 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) { @@ -472,6 +519,9 @@ public class NotificationManagerService extends SystemService { 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(); } @@ -726,6 +776,22 @@ public class NotificationManagerService extends SystemService { 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) { @@ -827,9 +893,9 @@ public class NotificationManagerService extends SystemService { } } } - 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(); } @@ -1058,8 +1124,9 @@ public class NotificationManagerService extends SystemService { @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, @@ -1089,7 +1156,7 @@ public class NotificationManagerService extends SystemService { mRankingHandler, mUsageStats, extractorNames); - mConditionProviders = new ConditionProviders(getContext(), mHandler, mUserProfiles); + mConditionProviders = conditionProviders; mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders); mZenModeHelper.addCallback(new ZenModeHelper.Callback() { @Override @@ -1145,16 +1212,14 @@ public class NotificationManagerService extends SystemService { } }); - 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) { @@ -1222,6 +1287,9 @@ public class NotificationManagerService extends SystemService { 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( @@ -1249,9 +1317,15 @@ public class NotificationManagerService extends SystemService { } }, 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); } @@ -1814,17 +1888,20 @@ public class NotificationManagerService extends SystemService { 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(); @@ -2468,7 +2545,8 @@ public class NotificationManagerService extends SystemService { 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; } } @@ -2491,7 +2569,8 @@ public class NotificationManagerService extends SystemService { } private boolean checkPackagePolicyAccess(String pkg) { - return mPolicyAccess.isPackageGranted(pkg); + return mConditionProviders.isPackageOrComponentAllowed( + pkg, getCallingUserHandle().getIdentifier()); } private boolean checkPolicyAccess(String pkg) { @@ -2602,29 +2681,19 @@ public class NotificationManagerService extends SystemService { } @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 @@ -2650,6 +2719,93 @@ public class NotificationManagerService extends SystemService { } @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(); @@ -2765,6 +2921,13 @@ public class NotificationManagerService extends SystemService { } 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) { @@ -3074,9 +3237,6 @@ public class NotificationManagerService extends SystemService { ZenLog.dump(pw, " "); } - pw.println("\n Policy access:"); - pw.print(" mPolicyAccess: "); pw.println(mPolicyAccess); - pw.println("\n Condition providers:"); mConditionProviders.dump(pw, filter); @@ -4759,6 +4919,13 @@ public class NotificationManagerService extends SystemService { return isUidSystemOrPhone(Binder.getCallingUid()); } + private void checkCallerIsSystemOrShell() { + if (Binder.getCallingUid() == Process.SHELL_UID) { + return; + } + checkCallerIsSystem(); + } + private void checkCallerIsSystem() { if (isCallerSystemOrPhone()) { return; @@ -4964,9 +5131,10 @@ public class NotificationManagerService extends SystemService { } 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 @@ -4974,6 +5142,7 @@ public class NotificationManagerService extends SystemService { 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; @@ -5071,11 +5240,13 @@ public class NotificationManagerService extends SystemService { } 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 @@ -5083,6 +5254,7 @@ public class NotificationManagerService extends SystemService { 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; @@ -5475,78 +5647,39 @@ public class NotificationManagerService extends SystemService { } } - 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); } } } diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index b3c6ff657867..bc617de7a3a6 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -169,7 +169,7 @@ public class RankingHelper implements RankingConfig { } 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) { @@ -182,14 +182,14 @@ public class RankingHelper implements RankingConfig { } 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 @@ -214,8 +214,8 @@ public class RankingHelper implements RankingConfig { 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); @@ -467,26 +467,6 @@ public class RankingHelper implements RankingConfig { 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. */ diff --git a/services/core/java/com/android/server/notification/XmlUtils.java b/services/core/java/com/android/server/notification/XmlUtils.java new file mode 100644 index 000000000000..831d03970fe3 --- /dev/null +++ b/services/core/java/com/android/server/notification/XmlUtils.java @@ -0,0 +1,52 @@ +/** + * 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; + } + } + +} diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java index 55d471919099..3c537713012e 100644 --- a/services/core/java/com/android/server/vr/VrManagerService.java +++ b/services/core/java/com/android/server/vr/VrManagerService.java @@ -21,6 +21,7 @@ import android.Manifest; 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; @@ -42,6 +43,7 @@ import android.os.Looper; 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; @@ -75,6 +77,7 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Date; +import java.util.List; import java.util.Objects; /** @@ -142,6 +145,7 @@ public class VrManagerService extends SystemService implements EnabledComponentC 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; /** @@ -593,6 +597,8 @@ public class VrManagerService extends SystemService implements EnabledComponentC @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); @@ -836,50 +842,28 @@ public class VrManagerService extends SystemService implements EnabledComponentC } 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) { diff --git a/services/tests/notification/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/notification/src/com/android/server/notification/ManagedServicesTest.java new file mode 100644 index 000000000000..ffbd8d493899 --- /dev/null +++ b/services/tests/notification/src/com/android/server/notification/ManagedServicesTest.java @@ -0,0 +1,700 @@ +/* + * 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) { + + } + } +} diff --git a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java index 6090e35ac8c2..a356ae0337e2 100644 --- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -60,11 +60,16 @@ import android.test.suitebuilder.annotation.SmallTest; 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; @@ -94,11 +99,16 @@ public class NotificationManagerServiceTest extends NotificationTestCase { @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; @@ -146,12 +156,24 @@ public class NotificationManagerServiceTest extends NotificationTestCase { // 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(); @@ -161,6 +183,11 @@ public class NotificationManagerServiceTest extends NotificationTestCase { PKG, new ParceledListSlice(Arrays.asList(mTestNotificationChannel))); } + @After + public void tearDown() throws Exception { + mFile.delete(); + } + public void waitForIdle() throws Exception { mTestableLooper.processAllMessages(); } @@ -951,4 +978,61 @@ public class NotificationManagerServiceTest extends NotificationTestCase { 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()); + } } diff --git a/services/tests/notification/src/com/android/server/notification/NotificationTestCase.java b/services/tests/notification/src/com/android/server/notification/NotificationTestCase.java index cc30aab68aa2..1ee34122f65a 100644 --- a/services/tests/notification/src/com/android/server/notification/NotificationTestCase.java +++ b/services/tests/notification/src/com/android/server/notification/NotificationTestCase.java @@ -27,7 +27,7 @@ public class NotificationTestCase { public final TestableContext mContext = new TestableContext(InstrumentationRegistry.getContext(), null); - protected Context getContext() { + protected TestableContext getContext() { return mContext; } } diff --git a/tests/testables/src/android/testing/TestableSettingsProvider.java b/tests/testables/src/android/testing/TestableSettingsProvider.java index fe97bca1ef41..5f2a2244b841 100644 --- a/tests/testables/src/android/testing/TestableSettingsProvider.java +++ b/tests/testables/src/android/testing/TestableSettingsProvider.java @@ -36,7 +36,7 @@ import static org.junit.Assert.*; 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; |