NAS Settings Migration

Change default NAS to AiAi for Pixel devices

Bug: 173106358
Test: manually on device, atest
Change-Id: I2fe0b3eed479188509df5ce65d1b2a1281e6c85a
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index dab5aff..35a9f9b 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -188,6 +188,8 @@
     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 c97f097..764b850 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9925,6 +9925,14 @@
                 "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 7d1ac95..8b29870 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 9736c4b..00fd05d 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 8c8d99e..2904325 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 f06a940..a48f76e 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -264,6 +264,10 @@
     // 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 c4a59c2..202b315 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 @@
             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 @@
         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 0000000..744a44f
--- /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 2f4cbd5..885643c 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.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 @@
     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 @@
         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 @@
         }
     };
 
+    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 @@
 
         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 @@
         getContext().unregisterReceiver(mNotificationTimeoutReceiver);
         getContext().unregisterReceiver(mRestoreReceiver);
         getContext().unregisterReceiver(mLocaleChangeReceiver);
+        getContext().unregisterReceiver(mNASIntentReceiver);
 
         if (mDeviceConfigChangedListener != null) {
             DeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigChangedListener);
@@ -2539,6 +2687,7 @@
             mConditionProviders.onBootPhaseAppsCanStart();
             mHistoryManager.onBootPhaseAppsCanStart();
             registerDeviceConfigChange();
+            migrateDefaultNASShowNotificationIfNecessary();
         } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
             mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis());
         }
@@ -5031,6 +5180,28 @@
         }
 
         @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 @@
         @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 @@
                 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 @@
             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 27a4826..c502ed5 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 @@
                 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 17c9411..c11ac3a 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 @@
 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.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 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 @@
     @Mock
     private ManagedServices.UserProfiles mUserProfiles;
 
+    private TestableContext mContext = spy(getContext());
     Object mLock = new Object();
 
 
@@ -82,10 +88,11 @@
     @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 @@
         profileIds.add(10);
         profileIds.add(12);
         when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds);
+        when(mNm.isNASMigrationDone(anyInt())).thenReturn(true);
     }
 
     @Test
@@ -189,4 +197,122 @@
         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 8be19f0..87efaa2 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.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 @@
     @Mock
     MultiRateLimiter mToastRateLimiter;
     BroadcastReceiver mPackageIntentReceiver;
+    BroadcastReceiver mNASIntentReceiver;
     NotificationRecordLoggerFake mNotificationRecordLogger = new NotificationRecordLoggerFake();
     private InstanceIdSequence mNotificationInstanceIdSequence = new InstanceIdSequenceFake(
             1 << 30);
@@ -526,6 +530,8 @@
 
         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 @@
                     && 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 @@
         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 @@
     }
 
     @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();