From 3f03f51421eb4d69d44528444f0821dd63bf922e Mon Sep 17 00:00:00 2001 From: Chloris Kuo Date: Fri, 19 Mar 2021 06:57:10 -0700 Subject: [PATCH] NAS Settings Migration Change default NAS to AiAi for Pixel devices Bug: 173106358 Test: manually on device, atest Change-Id: I2fe0b3eed479188509df5ce65d1b2a1281e6c85a --- .../android/app/INotificationManager.aidl | 2 + core/java/android/provider/Settings.java | 8 + core/res/AndroidManifest.xml | 6 + core/res/res/values/strings.xml | 15 ++ core/res/res/values/symbols.xml | 6 + proto/src/system_messages.proto | 4 + .../server/notification/ManagedServices.java | 5 +- .../notification/NASLearnMoreActivity.java | 51 ++++ .../NotificationManagerService.java | 204 ++++++++++++++- .../notification/ManagedServicesTest.java | 12 + .../NotificationAssistantsTest.java | 132 +++++++++- .../NotificationManagerServiceTest.java | 241 +++++++++++++++++- 12 files changed, 677 insertions(+), 9 deletions(-) create mode 100644 services/core/java/com/android/server/notification/NASLearnMoreActivity.java diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index dab5aff5c9a80..35a9f9b509cde 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -188,6 +188,8 @@ interface INotificationManager List getEnabledNotificationListeners(int userId); ComponentName getAllowedNotificationAssistantForUser(int userId); ComponentName getAllowedNotificationAssistant(); + ComponentName getDefaultNotificationAssistant(); + void resetDefaultNotificationAssistant(boolean loadFromConfig); boolean hasEnabledNotificationListener(String packageName, int userId); @UnsupportedAppUsage diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index c97f097e02c32..764b850feac89 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9924,6 +9924,14 @@ public final class Settings { public static final String CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS = "clipboard_show_access_notifications"; + /** + * If nonzero, nas has not been updated to reflect new changes. + * @hide + */ + @Readable + public static final String NAS_SETTINGS_UPDATED = "nas_settings_updated"; + + /** * These entries are considered common between the personal and the managed profile, * since the managed profile doesn't get to change them. diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 7d1ac95657fc6..8b2987039508a 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5982,6 +5982,12 @@ android:exported="false"> + + + diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 9736c4b199401..00fd05d1745f0 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5513,6 +5513,21 @@ This notification was ranked lower. Tap to provide feedback. + + + Try enhanced notifications + + To keep getting suggested actions, replies, and more, turn on enhanced notifications. Android Adaptive Notifications are no longer supported. + + Turn on + + Not now + + Learn more + + Enhanced notifications can read all notification content, including personal information like contact names and messages. This feature can also dismiss notifications or take actions on buttons in notifications, such as answering phone calls.\n\nThis feature can also turn Priority mode on or off and change related settings. + + Routine Mode info notification diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 8c8d99ec239ec..2904325cbb9fe 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2263,6 +2263,12 @@ + + + + + + diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index f06a94004110b..a48f76ea1ca3e 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -264,6 +264,10 @@ message SystemMessage { // Package: android NOTE_CARRIER_SUGGESTION_AVAILABLE = 63; + // Inform that NAS settings have changed on OS upgrade + // Package: android + NOTE_NAS_UPGRADE = 64; + // ADD_NEW_IDS_ABOVE_THIS_LINE // Legacy IDs with arbitrary values appear below // Legacy IDs existed as stable non-conflicting constants prior to the O release diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index c4a59c2655a25..202b315a7c822 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -600,11 +600,10 @@ abstract public class ManagedServices { throws XmlPullParserException, IOException { // read grants int type; - String version = ""; + String version = XmlUtils.readStringAttribute(parser, ATT_VERSION); readDefaults(parser); while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { String tag = parser.getName(); - version = XmlUtils.readStringAttribute(parser, ATT_VERSION); if (type == XmlPullParser.END_TAG && getConfig().xmlTag.equals(tag)) { break; @@ -642,7 +641,7 @@ abstract public class ManagedServices { rebindServices(false, USER_ALL); } - private void upgradeDefaultsXmlVersion() { + void upgradeDefaultsXmlVersion() { // check if any defaults are loaded int defaultsSize = mDefaultComponents.size() + mDefaultPackages.size(); if (defaultsSize == 0) { diff --git a/services/core/java/com/android/server/notification/NASLearnMoreActivity.java b/services/core/java/com/android/server/notification/NASLearnMoreActivity.java new file mode 100644 index 0000000000000..744a44f30ca7a --- /dev/null +++ b/services/core/java/com/android/server/notification/NASLearnMoreActivity.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.notification; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.WindowManager; + +import com.android.internal.R; + + +public class NASLearnMoreActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + showLearnMoreDialog(); + } + + private void showLearnMoreDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + AlertDialog alertDialog = builder.setMessage( + R.string.nas_upgrade_notification_learn_more_content) + .setPositiveButton(R.string.ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + NASLearnMoreActivity.this.finish(); + } + }) + .create(); + alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + alertDialog.show(); + } +} diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 2f4cbd558fb90..885643cf297a0 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -265,6 +265,7 @@ import com.android.internal.logging.InstanceIdSequence; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BackgroundThread; import com.android.internal.os.SomeArgs; @@ -619,6 +620,12 @@ public class NotificationManagerService extends SystemService { private NotificationRecordLogger mNotificationRecordLogger; private InstanceIdSequence mNotificationInstanceIdSequence; private Set mMsgPkgsAllowedAsConvos = new HashSet(); + protected static final String ACTION_ENABLE_NAS = + "android.server.notification.action.ENABLE_NAS"; + protected static final String ACTION_DISABLE_NAS = + "android.server.notification.action.DISABLE_NAS"; + protected static final String ACTION_LEARNMORE_NAS = + "android.server.notification.action.LEARNMORE_NAS"; static class Archive { final SparseArray mEnabled; @@ -725,6 +732,105 @@ public class NotificationManagerService extends SystemService { setDefaultAssistantForUser(userId); } + protected void migrateDefaultNASShowNotificationIfNecessary() { + final List activeUsers = mUm.getUsers(); + for (UserInfo userInfo : activeUsers) { + int userId = userInfo.getUserHandle().getIdentifier(); + if (isNASMigrationDone(userId)) { + continue; + } + if (mAssistants.hasUserSet(userId)) { + mAssistants.loadDefaultsFromConfig(false); + ComponentName defaultFromConfig = mAssistants.getDefaultFromConfig(); + List allowedComponents = mAssistants.getAllowedComponents(userId); + if (allowedComponents.contains(defaultFromConfig)) { + setNASMigrationDone(userId); + mAssistants.resetDefaultFromConfig(); + continue; + } + //User selected different NAS or none, need onboarding + enqueueNotificationInternal(getContext().getPackageName(), + getContext().getOpPackageName(), Binder.getCallingUid(), + Binder.getCallingPid(), TAG, + SystemMessageProto.SystemMessage.NOTE_NAS_UPGRADE, + createNASUpgradeNotification(userId), userId); + } + } + } + + protected Notification createNASUpgradeNotification(int userId) { + final Bundle extras = new Bundle(); + extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, + getContext().getResources().getString(R.string.global_action_settings)); + int title = R.string.nas_upgrade_notification_title; + int content = R.string.nas_upgrade_notification_content; + + Intent onboardingIntent = new Intent(Settings.ACTION_NOTIFICATION_ASSISTANT_SETTINGS); + onboardingIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + + Intent enableIntent = new Intent(ACTION_ENABLE_NAS); + enableIntent.putExtra(Intent.EXTRA_USER_ID, userId); + PendingIntent enableNASPendingIntent = PendingIntent.getBroadcast(getContext(), + 0, enableIntent, PendingIntent.FLAG_IMMUTABLE); + + Intent disableIntent = new Intent(ACTION_DISABLE_NAS); + disableIntent.putExtra(Intent.EXTRA_USER_ID, userId); + PendingIntent disableNASPendingIntent = PendingIntent.getBroadcast(getContext(), + 0, disableIntent, PendingIntent.FLAG_IMMUTABLE); + + Intent learnMoreIntent = new Intent(ACTION_LEARNMORE_NAS); + learnMoreIntent.putExtra(Intent.EXTRA_USER_ID, userId); + PendingIntent learnNASPendingIntent = PendingIntent.getBroadcast(getContext(), + 0, learnMoreIntent, PendingIntent.FLAG_IMMUTABLE); + + Notification.Action enableNASAction = new Notification.Action.Builder( + 0, + getContext().getResources().getString( + R.string.nas_upgrade_notification_enable_action), + enableNASPendingIntent).build(); + + Notification.Action disableNASAction = new Notification.Action.Builder( + 0, + getContext().getResources().getString( + R.string.nas_upgrade_notification_disable_action), + disableNASPendingIntent).build(); + + Notification.Action learnMoreNASAction = new Notification.Action.Builder( + 0, + getContext().getResources().getString( + R.string.nas_upgrade_notification_learn_more_action), + learnNASPendingIntent).build(); + + + return new Notification.Builder(getContext(), SystemNotificationChannels.ALERTS) + .setAutoCancel(false) + .setOngoing(true) + .setTicker(getContext().getResources().getString(title)) + .setSmallIcon(R.drawable.ic_settings_24dp) + .setContentTitle(getContext().getResources().getString(title)) + .setContentText(getContext().getResources().getString(content)) + .setContentIntent(PendingIntent.getActivity(getContext(), 0, onboardingIntent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE)) + .setLocalOnly(true) + .setStyle(new Notification.BigTextStyle()) + .addAction(enableNASAction) + .addAction(disableNASAction) + .addAction(learnMoreNASAction) + .build(); + } + + @VisibleForTesting + void setNASMigrationDone(int userId) { + Settings.Secure.putIntForUser(getContext().getContentResolver(), + Settings.Secure.NAS_SETTINGS_UPDATED, 1, userId); + } + + @VisibleForTesting + boolean isNASMigrationDone(int userId) { + return (Settings.Secure.getIntForUser(getContext().getContentResolver(), + Settings.Secure.NAS_SETTINGS_UPDATED, 0, userId) == 1); + } + protected void setDefaultAssistantForUser(int userId) { String overrideDefaultAssistantString = DeviceConfig.getProperty( DeviceConfig.NAMESPACE_SYSTEMUI, @@ -1750,6 +1856,41 @@ public class NotificationManagerService extends SystemService { } }; + private final BroadcastReceiver mNASIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, -1); + if (ACTION_ENABLE_NAS.equals(action)) { + mAssistants.resetDefaultFromConfig(); + setNotificationAssistantAccessGrantedForUserInternal( + CollectionUtils.firstOrNull(mAssistants.getDefaultComponents()), + userId, true, true); + setNASMigrationDone(userId); + cancelNotificationInternal(getContext().getPackageName(), + getContext().getOpPackageName(), Binder.getCallingUid(), + Binder.getCallingPid(), TAG, + SystemMessageProto.SystemMessage.NOTE_NAS_UPGRADE, userId); + } else if (ACTION_DISABLE_NAS.equals(action)) { + //Set default NAS to be null if user selected none during migration + mAssistants.clearDefaults(); + setNotificationAssistantAccessGrantedForUserInternal( + null, userId, true, true); + setNASMigrationDone(userId); + cancelNotificationInternal(getContext().getPackageName(), + getContext().getOpPackageName(), Binder.getCallingUid(), + Binder.getCallingPid(), TAG, + SystemMessageProto.SystemMessage.NOTE_NAS_UPGRADE, userId); + } else if (ACTION_LEARNMORE_NAS.equals(action)) { + Intent i = new Intent(getContext(), NASLearnMoreActivity.class); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + getContext().sendBroadcastAsUser( + new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), UserHandle.of(userId)); + getContext().startActivity(i); + } + } + }; + private final class SettingsObserver extends ContentObserver { private final Uri NOTIFICATION_BADGING_URI = Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BADGING); @@ -2273,6 +2414,12 @@ public class NotificationManagerService extends SystemService { IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED); getContext().registerReceiver(mLocaleChangeReceiver, localeChangedFilter); + + IntentFilter nasFilter = new IntentFilter(); + nasFilter.addAction(ACTION_ENABLE_NAS); + nasFilter.addAction(ACTION_DISABLE_NAS); + nasFilter.addAction(ACTION_LEARNMORE_NAS); + getContext().registerReceiver(mNASIntentReceiver, nasFilter); } /** @@ -2284,6 +2431,7 @@ public class NotificationManagerService extends SystemService { getContext().unregisterReceiver(mNotificationTimeoutReceiver); getContext().unregisterReceiver(mRestoreReceiver); getContext().unregisterReceiver(mLocaleChangeReceiver); + getContext().unregisterReceiver(mNASIntentReceiver); if (mDeviceConfigChangedListener != null) { DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigChangedListener); @@ -2539,6 +2687,7 @@ public class NotificationManagerService extends SystemService { mConditionProviders.onBootPhaseAppsCanStart(); mHistoryManager.onBootPhaseAppsCanStart(); registerDeviceConfigChange(); + migrateDefaultNASShowNotificationIfNecessary(); } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis()); } @@ -5030,6 +5179,28 @@ public class NotificationManagerService extends SystemService { return getAllowedNotificationAssistantForUser(getCallingUserHandle().getIdentifier()); } + @Override + public ComponentName getDefaultNotificationAssistant() { + checkCallerIsSystem(); + ArraySet defaultComponents = mAssistants.getDefaultComponents(); + if (defaultComponents.size() > 1) { + Slog.w(TAG, "More than one default NotificationAssistant: " + + defaultComponents.size()); + } + return CollectionUtils.firstOrNull(defaultComponents); + } + + @Override + public void resetDefaultNotificationAssistant(boolean loadFromConfig) { + checkCallerIsSystem(); + if (loadFromConfig) { + mAssistants.resetDefaultFromConfig(); + } else { + mAssistants.clearDefaults(); + } + } + + @Override public boolean hasEnabledNotificationListener(String packageName, int userId) { checkCallerIsSystem(); @@ -9219,8 +9390,14 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mLock") private Set mAllowedAdjustments = new ArraySet<>(); + protected ComponentName mDefaultFromConfig = null; + @Override protected void loadDefaultsFromConfig() { + loadDefaultsFromConfig(true); + } + + protected void loadDefaultsFromConfig(boolean addToDefault) { ArraySet assistants = new ArraySet<>(); assistants.addAll(Arrays.asList(mContext.getResources().getString( com.android.internal.R.string.config_defaultAssistantAccessComponent) @@ -9238,11 +9415,22 @@ public class NotificationManagerService extends SystemService { ArraySet approved = queryPackageForServices(packageName, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, USER_SYSTEM); if (approved.contains(assistantCn)) { - addDefaultComponentOrPackage(assistantCn.flattenToString()); + if (addToDefault) { + // add the default loaded from config file to mDefaultComponents and + // mDefaultPackages + addDefaultComponentOrPackage(assistantCn.flattenToString()); + } else { + // otherwise, store in the mDefaultFromConfig for NAS settings migration + mDefaultFromConfig = assistantCn; + } } } } + ComponentName getDefaultFromConfig() { + return mDefaultFromConfig; + } + public NotificationAssistants(Context context, Object lock, UserProfiles up, IPackageManager pm) { super(context, lock, up, pm); @@ -9724,12 +9912,26 @@ public class NotificationManagerService extends SystemService { for (UserInfo userInfo : activeUsers) { int userId = userInfo.getUserHandle().getIdentifier(); if (!hasUserSet(userId)) { + if (!isNASMigrationDone(userId)) { + resetDefaultFromConfig(); + setNASMigrationDone(userId); + } Slog.d(TAG, "Approving default notification assistant for user " + userId); setDefaultAssistantForUser(userId); } } } + protected void resetDefaultFromConfig() { + clearDefaults(); + loadDefaultsFromConfig(); + } + + protected void clearDefaults() { + mDefaultComponents.clear(); + mDefaultPackages.clear(); + } + @Override protected void setPackageOrComponentEnabled(String pkgOrComponent, int userId, boolean isPrimary, boolean enabled, boolean userSet) { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java index 27a48269f68cd..c502ed5d5984d 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java @@ -1416,6 +1416,18 @@ public class ManagedServicesTest extends UiServiceTestCase { new ArraySet(Arrays.asList(new ComponentName("xml", "class")))); } + @Test + public void loadDefaults_versionLatest_NoLoadDefaults() throws Exception { + resetComponentsAndPackages(); + mDefaults.add(new ComponentName("default", "class")); + mDefaultsString = "xml/class"; + mVersionString = String.valueOf(mService.DB_VERSION); + loadXml(mService); + assertEquals(mService.getDefaultComponents(), + new ArraySet(Arrays.asList(new ComponentName("xml", "class")))); + } + + private void resetComponentsAndPackages() { ArrayMap> empty = new ArrayMap(1); ArrayMap emptyPkgs = new ArrayMap(0); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java index 17c9411798531..c11ac3a3e3a18 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java @@ -18,10 +18,12 @@ package com.android.server.notification; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; +import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -38,6 +40,8 @@ import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.os.UserHandle; import android.os.UserManager; +import android.testing.TestableContext; +import android.util.ArraySet; import android.util.IntArray; import android.util.TypedXmlPullParser; import android.util.Xml; @@ -53,6 +57,7 @@ import org.mockito.MockitoAnnotations; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; public class NotificationAssistantsTest extends UiServiceTestCase { @@ -73,6 +78,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase { @Mock private ManagedServices.UserProfiles mUserProfiles; + private TestableContext mContext = spy(getContext()); Object mLock = new Object(); @@ -82,10 +88,11 @@ public class NotificationAssistantsTest extends UiServiceTestCase { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - getContext().setMockPackageManager(mPm); - getContext().addMockSystemService(Context.USER_SERVICE, mUm); - mAssistants = spy(mNm.new NotificationAssistants(getContext(), mLock, mUserProfiles, miPm)); + mContext.setMockPackageManager(mPm); + mContext.addMockSystemService(Context.USER_SERVICE, mUm); + mAssistants = spy(mNm.new NotificationAssistants(mContext, mLock, mUserProfiles, miPm)); when(mNm.getBinderService()).thenReturn(mINm); + mContext.ensureTestableResources(); List approved = new ArrayList<>(); ResolveInfo resolve = new ResolveInfo(); @@ -113,6 +120,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase { profileIds.add(10); profileIds.add(12); when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds); + when(mNm.isNASMigrationDone(anyInt())).thenReturn(true); } @Test @@ -189,4 +197,122 @@ public class NotificationAssistantsTest extends UiServiceTestCase { verify(mNm, never()).setNotificationAssistantAccessGrantedForUserInternal( any(ComponentName.class), eq(mZero.id), anyBoolean(), anyBoolean()); } + + @Test + public void testLoadDefaultsFromConfig() { + ComponentName oldDefaultComponent = ComponentName.unflattenFromString("package/Component1"); + ComponentName newDefaultComponent = ComponentName.unflattenFromString("package/Component2"); + + doReturn(new ArraySet<>(Arrays.asList(oldDefaultComponent, newDefaultComponent))) + .when(mAssistants).queryPackageForServices(any(), anyInt(), anyInt()); + // Test loadDefaultsFromConfig() add the config value to mDefaultComponents instead of + // mDefaultFromConfig + when(mContext.getResources().getString( + com.android.internal.R.string.config_defaultAssistantAccessComponent)) + .thenReturn(oldDefaultComponent.flattenToString()); + mAssistants.loadDefaultsFromConfig(); + assertEquals(new ArraySet<>(Arrays.asList(oldDefaultComponent)), + mAssistants.getDefaultComponents()); + assertNull(mAssistants.getDefaultFromConfig()); + + // Test loadDefaultFromConfig(false) only updates the mDefaultFromConfig + when(mContext.getResources().getString( + com.android.internal.R.string.config_defaultAssistantAccessComponent)) + .thenReturn(newDefaultComponent.flattenToString()); + mAssistants.loadDefaultsFromConfig(false); + assertEquals(new ArraySet<>(Arrays.asList(oldDefaultComponent)), + mAssistants.getDefaultComponents()); + assertEquals(newDefaultComponent, mAssistants.getDefaultFromConfig()); + + // Test resetDefaultFromConfig updates the mDefaultComponents to new config value + mAssistants.resetDefaultFromConfig(); + assertEquals(new ArraySet<>(Arrays.asList(newDefaultComponent)), + mAssistants.getDefaultComponents()); + assertEquals(newDefaultComponent, mAssistants.getDefaultFromConfig()); + } + + @Test + public void testNASSettingUpgrade_userNotSet_differentDefaultNAS() { + ComponentName oldDefaultComponent = ComponentName.unflattenFromString("package/Component1"); + ComponentName newDefaultComponent = ComponentName.unflattenFromString("package/Component2"); + + when(mNm.isNASMigrationDone(anyInt())).thenReturn(false); + doReturn(new ArraySet<>(Arrays.asList(newDefaultComponent))) + .when(mAssistants).queryPackageForServices(any(), anyInt(), anyInt()); + when(mContext.getResources().getString( + com.android.internal.R.string.config_defaultAssistantAccessComponent)) + .thenReturn(newDefaultComponent.flattenToString()); + + // User hasn't set the default NAS, set the oldNAS as the default with userSet=false here. + mAssistants.setPackageOrComponentEnabled(oldDefaultComponent.flattenToString(), + mZero.id, true, true /*enabled*/, false /*userSet*/); + + + // The migration for userSet==false happens in resetDefaultAssistantsIfNecessary + mAssistants.resetDefaultAssistantsIfNecessary(); + + // Verify the migration happened: setDefaultAssistantForUser should be called to + // update defaults + verify(mNm, times(1)).setNASMigrationDone(eq(mZero.id)); + verify(mNm, times(1)).setDefaultAssistantForUser(eq(mZero.id)); + assertEquals(new ArraySet<>(Arrays.asList(newDefaultComponent)), + mAssistants.getDefaultComponents()); + + when(mNm.isNASMigrationDone(anyInt())).thenReturn(true); + + // Test resetDefaultAssistantsIfNecessary again since it will be called on every reboot + mAssistants.resetDefaultAssistantsIfNecessary(); + + // The migration should not happen again, the invoke time for migration should not increase + verify(mNm, times(1)).setNASMigrationDone(eq(mZero.id)); + // The invoke time outside migration part should increase by 1 + verify(mNm, times(2)).setDefaultAssistantForUser(eq(mZero.id)); + } + + @Test + public void testNASSettingUpgrade_userNotSet_sameDefaultNAS() { + ComponentName defaultComponent = ComponentName.unflattenFromString("package/Component1"); + + when(mNm.isNASMigrationDone(anyInt())).thenReturn(false); + doReturn(new ArraySet<>(Arrays.asList(defaultComponent))) + .when(mAssistants).queryPackageForServices(any(), anyInt(), anyInt()); + when(mContext.getResources().getString( + com.android.internal.R.string.config_defaultAssistantAccessComponent)) + .thenReturn(defaultComponent.flattenToString()); + + // User hasn't set the default NAS, set the oldNAS as the default with userSet=false here. + mAssistants.setPackageOrComponentEnabled(defaultComponent.flattenToString(), + mZero.id, true, true /*enabled*/, false /*userSet*/); + + // The migration for userSet==false happens in resetDefaultAssistantsIfNecessary + mAssistants.resetDefaultAssistantsIfNecessary(); + + verify(mNm, times(1)).setNASMigrationDone(eq(mZero.id)); + verify(mNm, times(1)).setDefaultAssistantForUser(eq(mZero.id)); + assertEquals(new ArraySet<>(Arrays.asList(defaultComponent)), + mAssistants.getDefaultComponents()); + } + + @Test + public void testNASSettingUpgrade_userNotSet_defaultNASNone() { + ComponentName oldDefaultComponent = ComponentName.unflattenFromString("package/Component1"); + when(mNm.isNASMigrationDone(anyInt())).thenReturn(false); + doReturn(new ArraySet<>(Arrays.asList(oldDefaultComponent))) + .when(mAssistants).queryPackageForServices(any(), anyInt(), anyInt()); + // New default is none + when(mContext.getResources().getString( + com.android.internal.R.string.config_defaultAssistantAccessComponent)) + .thenReturn(""); + + // User hasn't set the default NAS, set the oldNAS as the default with userSet=false here. + mAssistants.setPackageOrComponentEnabled(oldDefaultComponent.flattenToString(), + mZero.id, true, true /*enabled*/, false /*userSet*/); + + // The migration for userSet==false happens in resetDefaultAssistantsIfNecessary + mAssistants.resetDefaultAssistantsIfNecessary(); + + verify(mNm, times(1)).setNASMigrationDone(eq(mZero.id)); + verify(mNm, times(1)).setDefaultAssistantForUser(eq(mZero.id)); + assertEquals(new ArraySet<>(), mAssistants.getDefaultComponents()); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 8be19f002fa55..87efaa270ec46 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -58,10 +58,13 @@ import static android.service.notification.Adjustment.KEY_USER_SENTIMENT; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ONGOING; -import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_SILENT; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; +import static com.android.server.notification.NotificationManagerService.ACTION_DISABLE_NAS; +import static com.android.server.notification.NotificationManagerService.ACTION_ENABLE_NAS; +import static com.android.server.notification.NotificationManagerService.ACTION_LEARNMORE_NAS; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; @@ -313,6 +316,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Mock MultiRateLimiter mToastRateLimiter; BroadcastReceiver mPackageIntentReceiver; + BroadcastReceiver mNASIntentReceiver; NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake(); private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake( 1 << 30); @@ -526,6 +530,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mContext, atLeastOnce()).registerReceiverAsUser(broadcastReceiverCaptor.capture(), any(), intentFilterCaptor.capture(), any(), any()); + verify(mContext, atLeastOnce()).registerReceiver(broadcastReceiverCaptor.capture(), + intentFilterCaptor.capture()); List broadcastReceivers = broadcastReceiverCaptor.getAllValues(); List intentFilters = intentFilterCaptor.getAllValues(); @@ -535,10 +541,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { && filter.hasAction(Intent.ACTION_PACKAGES_UNSUSPENDED) && filter.hasAction(Intent.ACTION_PACKAGES_SUSPENDED)) { mPackageIntentReceiver = broadcastReceivers.get(i); - break; + } else if (filter.hasAction(ACTION_ENABLE_NAS) + && filter.hasAction(ACTION_DISABLE_NAS) + && filter.hasAction(ACTION_LEARNMORE_NAS)) { + mNASIntentReceiver = broadcastReceivers.get(i); } } assertNotNull("package intent receiver should exist", mPackageIntentReceiver); + assertNotNull("nas intent receiver should exist", mNASIntentReceiver); // Pretend the shortcut exists List shortcutInfos = new ArrayList<>(); @@ -632,6 +642,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mPackageIntentReceiver.onReceive(getContext(), intent); } + private void simulateNASUpgradeBroadcast(String action, int uid) { + final Bundle extras = new Bundle(); + extras.putInt(Intent.EXTRA_USER_ID, uid); + + final Intent intent = new Intent(action); + intent.putExtras(extras); + + mNASIntentReceiver.onReceive(getContext(), intent); + } + private ArrayMap> generateResetComponentValues() { ArrayMap> changed = new ArrayMap<>(); changed.put(true, new ArrayList<>()); @@ -5729,6 +5749,223 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { eq("device"), eq(0), eq(false), eq(true)); } + @Test + public void testNASSettingUpgrade_userSetNull_showOnBoarding() throws RemoteException { + ComponentName newDefaultComponent = ComponentName.unflattenFromString("package/Component1"); + TestableNotificationManagerService service = spy(mService); + int userId = 11; + setUsers(new int[]{userId}); + setNASMigrationDone(false, userId); + when(mAssistants.getDefaultFromConfig()) + .thenReturn(newDefaultComponent); + when(mAssistants.getAllowedComponents(anyInt())) + .thenReturn(new ArrayList<>()); + when(mAssistants.hasUserSet(userId)).thenReturn(true); + + service.migrateDefaultNASShowNotificationIfNecessary(); + assertFalse(service.isNASMigrationDone(userId)); + verify(service, times(1)).createNASUpgradeNotification(eq(userId)); + verify(mAssistants, times(0)).resetDefaultFromConfig(); + + //Test user clear data before enable/disable from onboarding notification + ArrayMap> changedListeners = + generateResetComponentValues(); + when(mListeners.resetComponents(anyString(), anyInt())).thenReturn(changedListeners); + ArrayMap> changes = new ArrayMap<>(); + changes.put(true, new ArrayList(Arrays.asList(newDefaultComponent))); + changes.put(false, new ArrayList()); + when(mAssistants.resetComponents(anyString(), anyInt())).thenReturn(changes); + + //Clear data + service.getBinderService().clearData("package", userId, false); + //Test migrate flow again + service.migrateDefaultNASShowNotificationIfNecessary(); + + //The notification should be still there + assertFalse(service.isNASMigrationDone(userId)); + verify(service, times(2)).createNASUpgradeNotification(eq(userId)); + verify(mAssistants, times(0)).resetDefaultFromConfig(); + assertEquals(null, service.getApprovedAssistant(userId)); + } + + @Test + public void testNASSettingUpgrade_userSetDifferentDefault_showOnboarding() + throws RemoteException { + ComponentName oldDefaultComponent = ComponentName.unflattenFromString("package/Component1"); + ComponentName newDefaultComponent = ComponentName.unflattenFromString("package/Component2"); + TestableNotificationManagerService service = spy(mService); + int userId = 11; + setUsers(new int[]{userId}); + setNASMigrationDone(false, userId); + when(mAssistants.getDefaultComponents()) + .thenReturn(new ArraySet<>(Arrays.asList(oldDefaultComponent))); + when(mAssistants.getDefaultFromConfig()) + .thenReturn(newDefaultComponent); + when(mAssistants.getAllowedComponents(anyInt())) + .thenReturn(Arrays.asList(oldDefaultComponent)); + when(mAssistants.hasUserSet(userId)).thenReturn(true); + + service.migrateDefaultNASShowNotificationIfNecessary(); + assertFalse(service.isNASMigrationDone(userId)); + verify(service, times(1)).createNASUpgradeNotification(eq(userId)); + verify(mAssistants, times(0)).resetDefaultFromConfig(); + + //Test user clear data before enable/disable from onboarding notification + ArrayMap> changedListeners = + generateResetComponentValues(); + when(mListeners.resetComponents(anyString(), anyInt())).thenReturn(changedListeners); + ArrayMap> changes = new ArrayMap<>(); + changes.put(true, new ArrayList(Arrays.asList(newDefaultComponent))); + changes.put(false, new ArrayList()); + when(mAssistants.resetComponents(anyString(), anyInt())).thenReturn(changes); + + //Clear data + service.getBinderService().clearData("package", userId, false); + //Test migrate flow again + service.migrateDefaultNASShowNotificationIfNecessary(); + + //The notification should be still there + assertFalse(service.isNASMigrationDone(userId)); + verify(service, times(2)).createNASUpgradeNotification(eq(userId)); + verify(mAssistants, times(0)).resetDefaultFromConfig(); + assertEquals(oldDefaultComponent, service.getApprovedAssistant(userId)); + } + + @Test + public void testNASSettingUpgrade_multiUser() throws RemoteException { + ComponentName oldDefaultComponent = ComponentName.unflattenFromString("package/Component1"); + ComponentName newDefaultComponent = ComponentName.unflattenFromString("package/Component2"); + TestableNotificationManagerService service = spy(mService); + int userId1 = 11; + int userId2 = 12; + setUsers(new int[]{userId1, userId2}); + setNASMigrationDone(false, userId1); + setNASMigrationDone(false, userId2); + when(mAssistants.getDefaultComponents()) + .thenReturn(new ArraySet<>(Arrays.asList(oldDefaultComponent))); + when(mAssistants.getDefaultFromConfig()) + .thenReturn(newDefaultComponent); + //User1: need onboarding + when(mAssistants.getAllowedComponents(userId1)) + .thenReturn(Arrays.asList(oldDefaultComponent)); + //User2: no onboarding + when(mAssistants.getAllowedComponents(userId2)) + .thenReturn(Arrays.asList(newDefaultComponent)); + + when(mAssistants.hasUserSet(userId1)).thenReturn(true); + when(mAssistants.hasUserSet(userId2)).thenReturn(true); + + service.migrateDefaultNASShowNotificationIfNecessary(); + assertFalse(service.isNASMigrationDone(userId1)); + assertTrue(service.isNASMigrationDone(userId2)); + + verify(service, times(1)).createNASUpgradeNotification(any(Integer.class)); + // only user2's default get updated + verify(mAssistants, times(1)).resetDefaultFromConfig(); + } + + @Test + public void testNASSettingUpgrade_clearDataAfterMigrationIsDone() throws RemoteException { + ComponentName defaultComponent = ComponentName.unflattenFromString("package/Component"); + TestableNotificationManagerService service = spy(mService); + int userId = 12; + setUsers(new int[]{userId}); + when(mAssistants.getDefaultComponents()) + .thenReturn(new ArraySet<>(Arrays.asList(defaultComponent))); + when(mAssistants.hasUserSet(userId)).thenReturn(true); + setNASMigrationDone(true, userId); + + //Test User clear data + ArrayMap> changedListeners = + generateResetComponentValues(); + when(mListeners.resetComponents(anyString(), anyInt())).thenReturn(changedListeners); + ArrayMap> changes = new ArrayMap<>(); + changes.put(true, new ArrayList(Arrays.asList(defaultComponent))); + changes.put(false, new ArrayList()); + when(mAssistants.resetComponents(anyString(), anyInt())).thenReturn(changes); + + //Clear data + service.getBinderService().clearData("package", userId, false); + //Test migrate flow again + service.migrateDefaultNASShowNotificationIfNecessary(); + + //The notification should not appear again + verify(service, times(0)).createNASUpgradeNotification(eq(userId)); + verify(mAssistants, times(0)).resetDefaultFromConfig(); + } + + @Test + public void testNASUpgradeNotificationDisableBroadcast() { + int userId = 11; + setUsers(new int[]{userId}); + TestableNotificationManagerService service = spy(mService); + setNASMigrationDone(false, userId); + + simulateNASUpgradeBroadcast(ACTION_DISABLE_NAS, userId); + + assertTrue(service.isNASMigrationDone(userId)); + // User disabled the NAS from notification, the default stored in xml should be null + // rather than the new default + verify(mAssistants, times(1)).clearDefaults(); + verify(mAssistants, times(0)).resetDefaultFromConfig(); + + //No more notification after disabled + service.migrateDefaultNASShowNotificationIfNecessary(); + verify(service, times(0)).createNASUpgradeNotification(eq(userId)); + } + + @Test + public void testNASUpgradeNotificationEnableBroadcast_multiUser() { + int userId1 = 11; + int userId2 = 12; + setUsers(new int[]{userId1, userId2}); + TestableNotificationManagerService service = spy(mService); + setNASMigrationDone(false, userId1); + setNASMigrationDone(false, userId2); + + simulateNASUpgradeBroadcast(ACTION_ENABLE_NAS, userId1); + + assertTrue(service.isNASMigrationDone(userId1)); + assertFalse(service.isNASMigrationDone(userId2)); + verify(mAssistants, times(1)).resetDefaultFromConfig(); + + service.migrateDefaultNASShowNotificationIfNecessary(); + verify(service, times(0)).createNASUpgradeNotification(eq(userId1)); + } + + @Test + public void testNASUpgradeNotificationLearnMoreBroadcast() { + int userId = 11; + setUsers(new int[]{userId}); + TestableNotificationManagerService service = spy(mService); + setNASMigrationDone(false, userId); + doNothing().when(mContext).startActivity(any()); + + simulateNASUpgradeBroadcast(ACTION_LEARNMORE_NAS, userId); + + verify(mContext, times(1)).startActivity(any(Intent.class)); + assertFalse(service.isNASMigrationDone(userId)); + verify(service, times(0)).createNASUpgradeNotification(eq(userId)); + verify(mAssistants, times(0)).resetDefaultFromConfig(); + } + + + private void setNASMigrationDone(boolean done, int userId) { + Settings.Secure.putIntForUser(mContext.getContentResolver(), + Settings.Secure.NAS_SETTINGS_UPDATED, done ? 1 : 0, userId); + } + + private void setUsers(int[] userIds) { + List users = new ArrayList<>(); + for (int id: userIds) { + users.add(new UserInfo(id, String.valueOf(id), 0)); + } + for (UserInfo user : users) { + when(mUm.getUserInfo(eq(user.id))).thenReturn(user); + } + when(mUm.getUsers()).thenReturn(users); + } + @Test public void clearDefaultListenersPackageShouldEnableIt() throws RemoteException { ArrayMap> changedAssistants =