diff options
author | Chloris Kuo <chloriskuo@google.com> | 2021-03-19 06:57:10 -0700 |
---|---|---|
committer | Chloris Kuo <chloriskuo@google.com> | 2021-04-09 11:21:33 -0700 |
commit | 3f03f51421eb4d69d44528444f0821dd63bf922e (patch) | |
tree | d5f2d7b7e1366fd872b7704e5b864b08b0cc3625 | |
parent | 748a568a4b33defbfb8ff93c8ade9da16569db94 (diff) |
NAS Settings Migration
Change default NAS to AiAi for Pixel devices
Bug: 173106358
Test: manually on device, atest
Change-Id: I2fe0b3eed479188509df5ce65d1b2a1281e6c85a
12 files changed, 677 insertions, 9 deletions
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index dab5aff5c9a8..35a9f9b509cd 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -188,6 +188,8 @@ interface INotificationManager List<ComponentName> 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 c97f097e02c3..764b850feac8 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9925,6 +9925,14 @@ public final class Settings { "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 7d1ac95657fc..8b2987039508 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -5982,6 +5982,12 @@ android:exported="false"> </activity> + <activity android:name="com.android.server.notification.NASLearnMoreActivity" + android:theme="@style/Theme.Dialog.Confirmation" + android:excludeFromRecents="true" + android:exported="false"> + </activity> + <receiver android:name="com.android.server.BootReceiver" android:exported="true" android:systemUserOnly="true"> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 9736c4b19940..00fd05d1745f 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -5513,6 +5513,21 @@ <!-- Content description of the demoted feedback icon in the notification. [CHAR LIMIT=NONE] --> <string name="notification_feedback_indicator_demoted">This notification was ranked lower. Tap to provide feedback.</string> + <!-- Notification Intelligence --> + <!-- Title of notification indicating notification intelligence settings have changed when upgrading to S [CHAR LIMIT=30] --> + <string name="nas_upgrade_notification_title">Try enhanced notifications</string> + <!-- Content of notification indicating users need to update the settings [CHAR LIMIT=NONE] --> + <string name="nas_upgrade_notification_content">To keep getting suggested actions, replies, and more, turn on enhanced notifications. Android Adaptive Notifications are no longer supported.</string> + <!-- Label of notification action button to turn on the notification intelligence [CHAR LIMIT=20] --> + <string name="nas_upgrade_notification_enable_action">Turn on</string> + <!-- Label of notification action button to turn off the notification intelligence [CHAR LIMIT=20] --> + <string name="nas_upgrade_notification_disable_action">Not now</string> + <!-- Label of notification action button to learn more about the notification intelligence settings [CHAR LIMIT=20] --> + <string name="nas_upgrade_notification_learn_more_action">Learn more</string> + <!-- Content of notification learn more dialog about the notification intelligence settings [CHAR LIMIT=NONE] --> + <string name="nas_upgrade_notification_learn_more_content">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.</string> + + <!-- Dynamic mode battery saver strings --> <!-- The user visible name of the notification channel for the routine mode battery saver fyi notification [CHAR_LIMIT=80]--> <string name="dynamic_mode_notification_channel_name">Routine Mode info notification</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 8c8d99ec239e..2904325cbb9f 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2263,6 +2263,12 @@ <java-symbol type="style" name="Animation.RecentApplications" /> <java-symbol type="integer" name="dock_enter_exit_duration" /> <java-symbol type="bool" name="config_battery_percentage_setting_available" /> + <java-symbol type="string" name="nas_upgrade_notification_title" /> + <java-symbol type="string" name="nas_upgrade_notification_content" /> + <java-symbol type="string" name="nas_upgrade_notification_enable_action" /> + <java-symbol type="string" name="nas_upgrade_notification_disable_action" /> + <java-symbol type="string" name="nas_upgrade_notification_learn_more_action" /> + <java-symbol type="string" name="nas_upgrade_notification_learn_more_content" /> <!-- ImfTest --> <java-symbol type="layout" name="auto_complete_list" /> diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index f06a94004110..a48f76ea1ca3 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 c4a59c2655a2..202b315a7c82 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 000000000000..744a44f30ca7 --- /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 2f4cbd558fb9..885643cf297a 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<String> 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<Boolean> mEnabled; @@ -725,6 +732,105 @@ public class NotificationManagerService extends SystemService { setDefaultAssistantForUser(userId); } + protected void migrateDefaultNASShowNotificationIfNecessary() { + final List<UserInfo> 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<ComponentName> 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()); } @@ -5031,6 +5180,28 @@ public class NotificationManagerService extends SystemService { } @Override + public ComponentName getDefaultNotificationAssistant() { + checkCallerIsSystem(); + ArraySet<ComponentName> 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(); return mListeners.isPackageAllowed(packageName, userId); @@ -9219,8 +9390,14 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mLock") private Set<String> mAllowedAdjustments = new ArraySet<>(); + protected ComponentName mDefaultFromConfig = null; + @Override protected void loadDefaultsFromConfig() { + loadDefaultsFromConfig(true); + } + + protected void loadDefaultsFromConfig(boolean addToDefault) { ArraySet<String> 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<ComponentName> 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 27a48269f68c..c502ed5d5984 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<Integer, ArrayMap<Integer, String>> empty = new ArrayMap(1); ArrayMap<Integer, String> 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 17c941179853..c11ac3a3e3a1 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<ResolveInfo> 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 8be19f002fa5..87efaa270ec4 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<BroadcastReceiver> broadcastReceivers = broadcastReceiverCaptor.getAllValues(); List<IntentFilter> 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<ShortcutInfo> 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<Boolean, ArrayList<ComponentName>> generateResetComponentValues() { ArrayMap<Boolean, ArrayList<ComponentName>> changed = new ArrayMap<>(); changed.put(true, new ArrayList<>()); @@ -5730,6 +5750,223 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @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<Boolean, ArrayList<ComponentName>> changedListeners = + generateResetComponentValues(); + when(mListeners.resetComponents(anyString(), anyInt())).thenReturn(changedListeners); + ArrayMap<Boolean, ArrayList<ComponentName>> 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<Boolean, ArrayList<ComponentName>> changedListeners = + generateResetComponentValues(); + when(mListeners.resetComponents(anyString(), anyInt())).thenReturn(changedListeners); + ArrayMap<Boolean, ArrayList<ComponentName>> 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<Boolean, ArrayList<ComponentName>> changedListeners = + generateResetComponentValues(); + when(mListeners.resetComponents(anyString(), anyInt())).thenReturn(changedListeners); + ArrayMap<Boolean, ArrayList<ComponentName>> 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<UserInfo> 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<Boolean, ArrayList<ComponentName>> changedAssistants = generateResetComponentValues(); |