WifiServiceImpl: notify user of apBand conversion

Notify users of softap config changes.  Some hardware is incompatible
with available options for softap configs.  When the user's stored data
is restored and a config change is detected, put up a Notification to
the user.  Tapping the notification opens the wifi tethering config
page.

Bug: 80251951
Test: frameworks/opt/net/wifi/tests/wifitests/runtests.sh
Test: manually confirmed configs are converted for different device
Test: manually confirmed notification is displayed
Test: manually confirmed tapping notification opens tether settings
Test: manually confirmed notification is not displayed when conversion
is not done
Change-Id: Ib1f45128d51fb9a6e634c838d722c111934d29ae
diff --git a/service/java/com/android/server/wifi/FrameworkFacade.java b/service/java/com/android/server/wifi/FrameworkFacade.java
index 15b9cc7..26cb4ff 100644
--- a/service/java/com/android/server/wifi/FrameworkFacade.java
+++ b/service/java/com/android/server/wifi/FrameworkFacade.java
@@ -136,17 +136,6 @@
        return true;
     }
 
-    /**
-     * Create a new instance of WifiApConfigStore.
-     * @param context reference to a Context
-     * @param backupManagerProxy reference to a BackupManagerProxy
-     * @return an instance of WifiApConfigStore
-     */
-    public WifiApConfigStore makeApConfigStore(Context context,
-                                               BackupManagerProxy backupManagerProxy) {
-        return new WifiApConfigStore(context, backupManagerProxy);
-    }
-
     public long getTxPackets(String iface) {
         return TrafficStats.getTxPackets(iface);
     }
diff --git a/service/java/com/android/server/wifi/WifiApConfigStore.java b/service/java/com/android/server/wifi/WifiApConfigStore.java
index 109c0a7..d21452c 100644
--- a/service/java/com/android/server/wifi/WifiApConfigStore.java
+++ b/service/java/com/android/server/wifi/WifiApConfigStore.java
@@ -17,15 +17,25 @@
 package com.android.server.wifi;
 
 import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
 import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.internal.notification.SystemNotificationChannels;
 
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
@@ -44,6 +54,10 @@
  */
 public class WifiApConfigStore {
 
+    // Intent when user has interacted with the softap settings change notification
+    public static final String ACTION_HOTSPOT_CONFIG_USER_TAPPED_CONTENT =
+            "com.android.server.wifi.WifiApConfigStoreUtil.HOTSPOT_CONFIG_USER_TAPPED_CONTENT";
+
     private static final String TAG = "WifiApConfigStore";
 
     private static final String DEFAULT_AP_CONFIG_FILE =
@@ -71,19 +85,26 @@
     private ArrayList<Integer> mAllowed2GChannel = null;
 
     private final Context mContext;
+    private final Handler mHandler;
     private final String mApConfigFile;
     private final BackupManagerProxy mBackupManagerProxy;
+    private final FrameworkFacade mFrameworkFacade;
     private boolean mRequiresApBandConversion = false;
 
-    WifiApConfigStore(Context context, BackupManagerProxy backupManagerProxy) {
-        this(context, backupManagerProxy, DEFAULT_AP_CONFIG_FILE);
+    WifiApConfigStore(Context context, Looper looper,
+            BackupManagerProxy backupManagerProxy, FrameworkFacade frameworkFacade) {
+        this(context, looper, backupManagerProxy, frameworkFacade, DEFAULT_AP_CONFIG_FILE);
     }
 
     WifiApConfigStore(Context context,
+                      Looper looper,
                       BackupManagerProxy backupManagerProxy,
+                      FrameworkFacade frameworkFacade,
                       String apConfigFile) {
         mContext = context;
+        mHandler = new Handler(looper);
         mBackupManagerProxy = backupManagerProxy;
+        mFrameworkFacade = frameworkFacade;
         mApConfigFile = apConfigFile;
 
         String ap2GChannelListStr = mContext.getResources().getString(
@@ -111,8 +132,30 @@
             /* Save the default configuration to persistent storage. */
             writeApConfiguration(mApConfigFile, mWifiApConfig);
         }
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_HOTSPOT_CONFIG_USER_TAPPED_CONTENT);
+        mContext.registerReceiver(
+                mBroadcastReceiver, filter, null /* broadcastPermission */, mHandler);
     }
 
+    private final BroadcastReceiver mBroadcastReceiver =
+            new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    // For now we only have one registered listener, but we easily could expand this
+                    // to support multiple signals.  Starting off with a switch to support trivial
+                    // expansion.
+                    switch(intent.getAction()) {
+                        case ACTION_HOTSPOT_CONFIG_USER_TAPPED_CONTENT:
+                            handleUserHotspotConfigTappedContent();
+                            break;
+                        default:
+                            Log.e(TAG, "Unknown action " + intent.getAction());
+                    }
+                }
+            };
+
     /**
      * Return the current soft access point configuration.
      */
@@ -145,6 +188,43 @@
         return mAllowed2GChannel;
     }
 
+    /**
+     * Helper method to create and send notification to user of apBand conversion.
+     *
+     * @param packageName name of the calling app
+     */
+    public void notifyUserOfApBandConversion(String packageName) {
+        Log.w(TAG, "ready to post notification - triggered by " + packageName);
+        Notification notification = createConversionNotification();
+        NotificationManager notificationManager = (NotificationManager)
+                    mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        notificationManager.notify(SystemMessage.NOTE_SOFTAP_CONFIG_CHANGED, notification);
+    }
+
+    private Notification createConversionNotification() {
+        CharSequence title = mContext.getText(R.string.wifi_softap_config_change);
+        CharSequence contentSummary = mContext.getText(R.string.wifi_softap_config_change_summary);
+        CharSequence content = mContext.getText(R.string.wifi_softap_config_change_detailed);
+        int color = mContext.getResources()
+                .getColor(R.color.system_notification_accent_color, mContext.getTheme());
+
+        return new Notification.Builder(mContext, SystemNotificationChannels.NETWORK_STATUS)
+                .setSmallIcon(R.drawable.ic_wifi_settings)
+                .setPriority(Notification.PRIORITY_HIGH)
+                .setCategory(Notification.CATEGORY_SYSTEM)
+                .setContentTitle(title)
+                .setContentText(contentSummary)
+                .setContentIntent(getPrivateBroadcast(ACTION_HOTSPOT_CONFIG_USER_TAPPED_CONTENT))
+                .setTicker(title)
+                .setShowWhen(false)
+                .setLocalOnly(true)
+                .setColor(color)
+                .setStyle(new Notification.BigTextStyle().bigText(content)
+                                                         .setBigContentTitle(title)
+                                                         .setSummaryText(contentSummary))
+                .build();
+    }
+
     private WifiConfiguration apBandCheckConvert(WifiConfiguration config) {
         if (mRequiresApBandConversion) {
             // some devices are unable to support 5GHz only operation, check for 5GHz and
@@ -386,4 +466,29 @@
 
         return true;
     }
+
+    /**
+     * Helper method to start up settings on the softap config page.
+     */
+    private void startSoftApSettings() {
+        mContext.startActivity(
+                new Intent("com.android.settings.WIFI_TETHER_SETTINGS")
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+    }
+
+    /**
+     * Helper method to trigger settings to open the softap config page
+     */
+    private void handleUserHotspotConfigTappedContent() {
+        startSoftApSettings();
+        NotificationManager notificationManager = (NotificationManager)
+                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        notificationManager.cancel(SystemMessage.NOTE_SOFTAP_CONFIG_CHANGED);
+    }
+
+    private PendingIntent getPrivateBroadcast(String action) {
+        Intent intent = new Intent(action).setPackage("android");
+        return mFrameworkFacade.getBroadcast(
+                mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+    }
 }
diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java
index 1995480..dbf730a 100644
--- a/service/java/com/android/server/wifi/WifiInjector.java
+++ b/service/java/com/android/server/wifi/WifiInjector.java
@@ -205,7 +205,8 @@
                 SystemProperties.get(BOOT_DEFAULT_WIFI_COUNTRY_CODE),
                 mContext.getResources()
                         .getBoolean(R.bool.config_wifi_revert_country_code_on_cellular_loss));
-        mWifiApConfigStore = new WifiApConfigStore(mContext, mBackupManagerProxy);
+        mWifiApConfigStore = new WifiApConfigStore(
+                mContext, wifiStateMachineLooper, mBackupManagerProxy, mFrameworkFacade);
 
         // WifiConfigManager/Store objects and their dependencies.
         // New config store
diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java
index a0bbded..cb16efd 100644
--- a/service/java/com/android/server/wifi/WifiServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -1583,6 +1583,21 @@
     }
 
     /**
+     * Method used to inform user of Ap Configuration conversion due to hardware.
+     */
+    @Override
+    public void notifyUserOfApBandConversion(String packageName) {
+        enforceNetworkSettingsPermission();
+
+        if (mVerboseLoggingEnabled) {
+            mLog.info("notifyUserOfApBandConversion uid=% packageName=%")
+                    .c(Binder.getCallingUid()).c(packageName).flush();
+        }
+
+        mWifiApConfigStore.notifyUserOfApBandConversion(packageName);
+    }
+
+    /**
      * see {@link android.net.wifi.WifiManager#isScanAlwaysAvailable()}
      */
     @Override
diff --git a/tests/wifitests/src/com/android/server/wifi/MockResources.java b/tests/wifitests/src/com/android/server/wifi/MockResources.java
index ab40c6f..1a061e2 100644
--- a/tests/wifitests/src/com/android/server/wifi/MockResources.java
+++ b/tests/wifitests/src/com/android/server/wifi/MockResources.java
@@ -23,11 +23,13 @@
     private HashMap<Integer, Boolean> mBooleanValues;
     private HashMap<Integer, Integer> mIntegerValues;
     private HashMap<Integer, String>  mStringValues;
+    private HashMap<Integer, CharSequence> mTextValues;
 
     public MockResources() {
         mBooleanValues = new HashMap<Integer, Boolean>();
         mIntegerValues = new HashMap<Integer, Integer>();
         mStringValues  = new HashMap<Integer, String>();
+        mTextValues    = new HashMap<Integer, CharSequence>();
     }
 
     @Override
@@ -57,6 +59,15 @@
         }
     }
 
+    @Override
+    public CharSequence getText(int id) {
+        if (mTextValues.containsKey(id))  {
+            return mTextValues.get(id);
+        } else {
+            return null;
+        }
+    }
+
     public void setBoolean(int id, boolean value) {
         mBooleanValues.put(id, value);
     }
@@ -68,4 +79,8 @@
     public void setString(int id, String value) {
         mStringValues.put(id, value);
     }
+
+    public void setText(int id, CharSequence value) {
+        mTextValues.put(id, value);
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java b/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java
index 1672dca..88312ce 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java
@@ -19,25 +19,38 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.KeyMgmt;
+import android.os.Build;
+import android.os.test.TestLooper;
 import android.support.test.filters.SmallTest;
 
 import com.android.internal.R;
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.io.File;
 import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Random;
 
 /**
@@ -54,19 +67,34 @@
     private static final String TEST_CONFIGURED_AP_SSID = "ConfiguredAP";
     private static final String TEST_DEFAULT_HOTSPOT_SSID = "TestShare";
     private static final String TEST_DEFAULT_HOTSPOT_PSK = "TestPassword";
+    private static final String TEST_APCONFIG_CHANGE_NOTIFICATION_TITLE = "Notification title";
+    private static final String TEST_APCONFIG_CHANGE_NOTIFICATION_SUMMARY = "Notification summary";
+    private static final String TEST_APCONFIG_CHANGE_NOTIFICATION_DETAILED =
+            "Notification detailed";
     private static final int RAND_SSID_INT_MIN = 1000;
     private static final int RAND_SSID_INT_MAX = 9999;
     private static final String TEST_CHAR_SET_AS_STRING = "abcdefghijklmnopqrstuvwxyz0123456789";
 
-    @Mock Context mContext;
-    @Mock BackupManagerProxy mBackupManagerProxy;
-    File mApConfigFile;
-    Random mRandom;
-    MockResources mResources;
+    @Mock private Context mContext;
+    private TestLooper mLooper;
+    @Mock private BackupManagerProxy mBackupManagerProxy;
+    @Mock private FrameworkFacade mFrameworkFacade;
+    private File mApConfigFile;
+    private Random mRandom;
+    private MockResources mResources;
+    @Mock private ApplicationInfo mMockApplInfo;
+    private BroadcastReceiver mBroadcastReceiver;
+    @Mock private NotificationManager mNotificationManager;
+    private ArrayList<Integer> mKnownGood2GChannelList;
 
     @Before
     public void setUp() throws Exception {
+        mLooper = new TestLooper();
         MockitoAnnotations.initMocks(this);
+        when(mContext.getSystemService(Context.NOTIFICATION_SERVICE))
+                .thenReturn(mNotificationManager);
+        mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.P;
+        when(mContext.getApplicationInfo()).thenReturn(mMockApplInfo);
 
         /* Create a temporary file for AP config file storage. */
         mApConfigFile = File.createTempFile(TEST_AP_CONFIG_FILE_PREFIX, "");
@@ -74,15 +102,24 @@
         /* Setup expectations for Resources to return some default settings. */
         mResources = new MockResources();
         mResources.setString(R.string.config_wifi_framework_sap_2G_channel_list,
-                            TEST_DEFAULT_2G_CHANNEL_LIST);
+                             TEST_DEFAULT_2G_CHANNEL_LIST);
         mResources.setString(R.string.wifi_tether_configure_ssid_default,
-                            TEST_DEFAULT_AP_SSID);
+                             TEST_DEFAULT_AP_SSID);
         mResources.setString(R.string.wifi_localhotspot_configure_ssid_default,
-                            TEST_DEFAULT_HOTSPOT_SSID);
+                             TEST_DEFAULT_HOTSPOT_SSID);
         /* Default to device that does not require ap band conversion */
         mResources.setBoolean(R.bool.config_wifi_convert_apband_5ghz_to_any, false);
+        mResources.setText(R.string.wifi_softap_config_change,
+                           TEST_APCONFIG_CHANGE_NOTIFICATION_TITLE);
+        mResources.setText(R.string.wifi_softap_config_change_summary,
+                           TEST_APCONFIG_CHANGE_NOTIFICATION_SUMMARY);
+        mResources.setText(R.string.wifi_softap_config_change_detailed,
+                           TEST_APCONFIG_CHANGE_NOTIFICATION_DETAILED);
         when(mContext.getResources()).thenReturn(mResources);
 
+        // build the known good 2G channel list: TEST_DEFAULT_2G_CHANNEL_LIST
+        mKnownGood2GChannelList = new ArrayList(Arrays.asList(1, 2, 3, 4, 5, 6));
+
         mRandom = new Random();
     }
 
@@ -93,6 +130,22 @@
     }
 
     /**
+     * Helper method to create and verify actions for the ApConfigStore used in the following tests.
+     */
+    private WifiApConfigStore createWifiApConfigStore() {
+        WifiApConfigStore store = new WifiApConfigStore(
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
+
+        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
+                ArgumentCaptor.forClass(BroadcastReceiver.class);
+        verify(mContext).registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
+        mBroadcastReceiver = broadcastReceiverCaptor.getValue();
+
+        return store;
+    }
+
+    /**
      * Generate a WifiConfiguration based on the specified parameters.
      */
     private WifiConfiguration setupApConfig(
@@ -149,7 +202,8 @@
     @Test
     public void initWithDefaultConfiguration() throws Exception {
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyDefaultApConfig(store.getApConfiguration(), TEST_DEFAULT_AP_SSID);
     }
 
@@ -167,7 +221,8 @@
                 40                 /* AP channel */);
         writeApConfigFile(expectedConfig);
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyApConfig(expectedConfig, store.getApConfiguration());
     }
 
@@ -187,7 +242,8 @@
                 40                 /* AP channel */);
         writeApConfigFile(expectedConfig);
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyApConfig(expectedConfig, store.getApConfiguration());
 
         store.setApConfiguration(null);
@@ -202,7 +258,8 @@
     public void updateApConfiguration() throws Exception {
         /* Initialize WifiApConfigStore with default configuration. */
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyDefaultApConfig(store.getApConfiguration(), TEST_DEFAULT_AP_SSID);
 
         /* Update with a valid configuration. */
@@ -226,7 +283,8 @@
     public void convertSingleModeDeviceAnyTo5Ghz() throws Exception {
         /* Initialize WifiApConfigStore with default configuration. */
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyDefaultApConfig(store.getApConfiguration(), TEST_DEFAULT_AP_SSID);
 
         /* Update with a valid configuration. */
@@ -257,7 +315,8 @@
     public void singleModeDevice5GhzNotConverted() throws Exception {
         /* Initialize WifiApConfigStore with default configuration. */
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyDefaultApConfig(store.getApConfiguration(), TEST_DEFAULT_AP_SSID);
 
         /* Update with a valid configuration. */
@@ -282,7 +341,8 @@
 
         /* Initialize WifiApConfigStore with default configuration. */
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyDefaultApConfig(store.getApConfiguration(), TEST_DEFAULT_AP_SSID);
 
         /* Update with a valid configuration. */
@@ -315,7 +375,8 @@
 
         /* Initialize WifiApConfigStore with default configuration. */
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyDefaultApConfig(store.getApConfiguration(), TEST_DEFAULT_AP_SSID);
 
         /* Update with a valid configuration. */
@@ -353,7 +414,8 @@
 
         writeApConfigFile(persistedConfig);
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyApConfig(expectedConfig, store.getApConfiguration());
         verify(mBackupManagerProxy).notifyDataChanged();
     }
@@ -374,7 +436,8 @@
 
         writeApConfigFile(persistedConfig);
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyApConfig(persistedConfig, store.getApConfiguration());
         verify(mBackupManagerProxy, never()).notifyDataChanged();
     }
@@ -403,7 +466,8 @@
 
         writeApConfigFile(persistedConfig);
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyApConfig(expectedConfig, store.getApConfiguration());
         verify(mBackupManagerProxy).notifyDataChanged();
     }
@@ -426,7 +490,8 @@
 
         writeApConfigFile(persistedConfig);
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         verifyApConfig(persistedConfig, store.getApConfiguration());
         verify(mBackupManagerProxy, never()).notifyDataChanged();
     }
@@ -437,7 +502,8 @@
     @Test
     public void getDefaultApConfigurationIsValid() {
         WifiApConfigStore store = new WifiApConfigStore(
-                mContext, mBackupManagerProxy, mApConfigFile.getPath());
+                mContext, mLooper.getLooper(), mBackupManagerProxy, mFrameworkFacade,
+                mApConfigFile.getPath());
         WifiConfiguration config = store.getApConfiguration();
         assertTrue(WifiApConfigStore.validateApWifiConfiguration(config));
     }
@@ -597,4 +663,51 @@
         config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
         assertFalse(WifiApConfigStore.validateApWifiConfiguration(config));
     }
+
+    /**
+     * Verify the default 2GHz channel list is properly returned.
+     */
+    @Test
+    public void testDefault2GHzChannelListReturned() {
+        // first build known good list
+        WifiApConfigStore store = createWifiApConfigStore();
+        ArrayList<Integer> channels = store.getAllowed2GChannel();
+
+        assertEquals(mKnownGood2GChannelList.size(), channels.size());
+        for (int channel : channels) {
+            assertTrue(mKnownGood2GChannelList.contains(channel));
+        }
+    }
+
+    /**
+     * Verify a notification is posted when triggered when the ap config was converted.
+     */
+    @Test
+    public void testNotifyUserOfApBandConversion() throws Exception {
+        WifiApConfigStore store = createWifiApConfigStore();
+        store.notifyUserOfApBandConversion(TAG);
+        // verify the notification is posted
+        ArgumentCaptor<Notification> notificationCaptor =
+                ArgumentCaptor.forClass(Notification.class);
+        verify(mNotificationManager).notify(eq(SystemMessage.NOTE_SOFTAP_CONFIG_CHANGED),
+                                            notificationCaptor.capture());
+        Notification notification = notificationCaptor.getValue();
+        assertEquals(TEST_APCONFIG_CHANGE_NOTIFICATION_TITLE,
+                     notification.extras.getCharSequence(Notification.EXTRA_TITLE));
+        assertEquals(TEST_APCONFIG_CHANGE_NOTIFICATION_DETAILED,
+                     notification.extras.getCharSequence(Notification.EXTRA_BIG_TEXT));
+        assertEquals(TEST_APCONFIG_CHANGE_NOTIFICATION_SUMMARY,
+                     notification.extras.getCharSequence(Notification.EXTRA_SUMMARY_TEXT));
+    }
+
+    /**
+     * Verify the posted notification is cleared when the user interacts with it.
+     */
+    @Test
+    public void testNotificationClearedWhenContentIsTapped() throws Exception {
+        WifiApConfigStore store = createWifiApConfigStore();
+        Intent intent = new Intent(WifiApConfigStore.ACTION_HOTSPOT_CONFIG_USER_TAPPED_CONTENT);
+        mBroadcastReceiver.onReceive(mContext, intent);
+        verify(mNotificationManager).cancel(eq(SystemMessage.NOTE_SOFTAP_CONFIG_CHANGED));
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
index 6a24816..f04f531 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiServiceImplTest.java
@@ -2838,4 +2838,31 @@
         mBroadcastReceiverCaptor.getValue().onReceive(mContext, intent);
         verifyNoMoreInteractions(mWifiCountryCode);
     }
+
+    /**
+     * Verify calls to notify users of a softap config change check the NETWORK_SETTINGS permission.
+     */
+    @Test
+    public void testNotifyUserOfApBandConversionChecksNetworkSettingsPermission() {
+        mWifiServiceImpl.notifyUserOfApBandConversion(TEST_PACKAGE_NAME);
+        verify(mContext).enforceCallingOrSelfPermission(
+                eq(android.Manifest.permission.NETWORK_SETTINGS),
+                eq("WifiService"));
+        verify(mWifiApConfigStore).notifyUserOfApBandConversion(eq(TEST_PACKAGE_NAME));
+    }
+
+    /**
+     * Verify calls to notify users do not trigger a notification when NETWORK_SETTINGS is not held
+     * by the caller.
+     */
+    @Test
+    public void testNotifyUserOfApBandConversionThrowsExceptionWithoutNetworkSettingsPermission() {
+        doThrow(new SecurityException()).when(mContext)
+                .enforceCallingOrSelfPermission(eq(android.Manifest.permission.NETWORK_SETTINGS),
+                                                eq("WifiService"));
+        try {
+            mWifiServiceImpl.notifyUserOfApBandConversion(TEST_PACKAGE_NAME);
+            fail("Expected Security exception");
+        } catch (SecurityException e) { }
+    }
 }