Merge "Add permitted_packages to framework-tethering."
diff --git a/Tethering/common/TetheringLib/api/system-current.txt b/Tethering/common/TetheringLib/api/system-current.txt
index 2f5ea6a..edd1ebb 100644
--- a/Tethering/common/TetheringLib/api/system-current.txt
+++ b/Tethering/common/TetheringLib/api/system-current.txt
@@ -23,7 +23,6 @@
     method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheringEventCallback);
     method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener);
     method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(@NonNull android.net.TetheringManager.TetheringRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback);
-    method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(int, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback);
     method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void stopAllTethering();
     method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void stopTethering(int);
     method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.ACCESS_NETWORK_STATE}) public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback);
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index b70165a..096f4fe 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -739,11 +739,13 @@
      * @param type The tethering type, on of the {@code TetheringManager#TETHERING_*} constants.
      * @param executor {@link Executor} to specify the thread upon which the callback of
      *         TetheringRequest will be invoked.
+     * @hide
      */
     @RequiresPermission(anyOf = {
             android.Manifest.permission.TETHER_PRIVILEGED,
             android.Manifest.permission.WRITE_SETTINGS
     })
+    @SystemApi(client = MODULE_LIBRARIES)
     public void startTethering(int type, @NonNull final Executor executor,
             @NonNull final StartTetheringCallback callback) {
         startTethering(new TetheringRequest.Builder(type).build(), executor, callback);
diff --git a/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
index 3dcc15f..020db54 100644
--- a/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
+++ b/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -303,7 +303,8 @@
 
         final UserManager userManager = (UserManager) mContext.getSystemService(
                 Context.USER_SERVICE);
-        mTetheringRestriction = new UserRestrictionActionListener(userManager, this);
+        mTetheringRestriction = new UserRestrictionActionListener(
+                userManager, this, mNotificationUpdater);
         mExecutor = new TetheringThreadExecutor(mHandler);
         mActiveDataSubIdListener = new ActiveDataSubIdListener(mExecutor);
 
@@ -369,9 +370,10 @@
 
             mActiveDataSubId = subId;
             updateConfiguration();
+            mNotificationUpdater.onActiveDataSubscriptionIdChanged(subId);
             // To avoid launching unexpected provisioning checks, ignore re-provisioning
             // when no CarrierConfig loaded yet. Assume reevaluateSimCardProvisioning()
-            // ill be triggered again when CarrierConfig is loaded.
+            // will be triggered again when CarrierConfig is loaded.
             if (mEntitlementMgr.getCarrierConfig(mConfig) != null) {
                 mEntitlementMgr.reevaluateSimCardProvisioning(mConfig);
             } else {
@@ -431,9 +433,7 @@
         // Called by wifi when the number of soft AP clients changed.
         @Override
         public void onConnectedClientsChanged(final List<WifiClient> clients) {
-            if (mConnectedClientsTracker.updateConnectedClients(mForwardedDownstreams, clients)) {
-                reportTetherClientsChanged(mConnectedClientsTracker.getLastTetheredClients());
-            }
+            updateConnectedClients(clients);
         }
     }
 
@@ -635,7 +635,10 @@
                 Context.ETHERNET_SERVICE);
         synchronized (mPublicSync) {
             if (enable) {
-                if (mEthernetCallback != null) return TETHER_ERROR_NO_ERROR;
+                if (mEthernetCallback != null) {
+                    Log.d(TAG, "Ethernet tethering already started");
+                    return TETHER_ERROR_NO_ERROR;
+                }
 
                 mEthernetCallback = new EthernetCallback();
                 mEthernetIfaceRequest = em.requestTetheredInterface(mExecutor, mEthernetCallback);
@@ -996,11 +999,14 @@
     protected static class UserRestrictionActionListener {
         private final UserManager mUserManager;
         private final Tethering mWrapper;
+        private final TetheringNotificationUpdater mNotificationUpdater;
         public boolean mDisallowTethering;
 
-        public UserRestrictionActionListener(UserManager um, Tethering wrapper) {
+        public UserRestrictionActionListener(@NonNull UserManager um, @NonNull Tethering wrapper,
+                @NonNull TetheringNotificationUpdater updater) {
             mUserManager = um;
             mWrapper = wrapper;
+            mNotificationUpdater = updater;
             mDisallowTethering = false;
         }
 
@@ -1019,13 +1025,21 @@
                 return;
             }
 
-            // TODO: Add user restrictions notification.
-            final boolean isTetheringActiveOnDevice = (mWrapper.getTetheredIfaces().length != 0);
-
-            if (newlyDisallowed && isTetheringActiveOnDevice) {
-                mWrapper.untetherAll();
-                // TODO(b/148139325): send tetheringSupported on restriction change
+            if (!newlyDisallowed) {
+                // Clear the restricted notification when user is allowed to have tethering
+                // function.
+                mNotificationUpdater.tetheringRestrictionLifted();
+                return;
             }
+
+            // Restricted notification is shown when tethering function is disallowed on
+            // user's device.
+            mNotificationUpdater.notifyTetheringDisabledByRestriction();
+
+            // Untether from all downstreams since tethering is disallowed.
+            mWrapper.untetherAll();
+
+            // TODO(b/148139325): send tetheringSupported on restriction change
         }
     }
 
@@ -1494,7 +1508,7 @@
             } else {
                 dnsServers = mConfig.defaultIPv4DNS;
             }
-            final int netId = (network != null) ? network.netId : NETID_UNSET;
+            final int netId = (network != null) ? network.getNetId() : NETID_UNSET;
             try {
                 mNetd.tetherDnsSet(netId, dnsServers);
                 mLog.log(String.format(
@@ -1559,6 +1573,7 @@
             mIPv6TetheringCoordinator.removeActiveDownstream(who);
             mOffload.excludeDownstreamInterface(who.interfaceName());
             mForwardedDownstreams.remove(who);
+            updateConnectedClients(null /* wifiClients */);
 
             // If this is a Wi-Fi interface, tell WifiManager of any errors
             // or the inactive serving state.
@@ -2141,6 +2156,12 @@
         return false;
     }
 
+    private void updateConnectedClients(final List<WifiClient> wifiClients) {
+        if (mConnectedClientsTracker.updateConnectedClients(mForwardedDownstreams, wifiClients)) {
+            reportTetherClientsChanged(mConnectedClientsTracker.getLastTetheredClients());
+        }
+    }
+
     private IpServer.Callback makeControlCallback() {
         return new IpServer.Callback() {
             @Override
@@ -2155,10 +2176,7 @@
 
             @Override
             public void dhcpLeasesChanged() {
-                if (mConnectedClientsTracker.updateConnectedClients(
-                        mForwardedDownstreams, null /* wifiClients */)) {
-                    reportTetherClientsChanged(mConnectedClientsTracker.getLastTetheredClients());
-                }
+                updateConnectedClients(null /* wifiClients */);
             }
         };
     }
diff --git a/Tethering/src/com/android/server/connectivity/tethering/TetheringNotificationUpdater.java b/Tethering/src/com/android/server/connectivity/tethering/TetheringNotificationUpdater.java
index b97f752..992cdd8 100644
--- a/Tethering/src/com/android/server/connectivity/tethering/TetheringNotificationUpdater.java
+++ b/Tethering/src/com/android/server/connectivity/tethering/TetheringNotificationUpdater.java
@@ -29,12 +29,14 @@
 import android.content.res.Resources;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
 
 import androidx.annotation.ArrayRes;
 import androidx.annotation.DrawableRes;
+import androidx.annotation.IntDef;
 import androidx.annotation.IntRange;
 import androidx.annotation.NonNull;
 
@@ -54,10 +56,15 @@
 public class TetheringNotificationUpdater {
     private static final String TAG = TetheringNotificationUpdater.class.getSimpleName();
     private static final String CHANNEL_ID = "TETHERING_STATUS";
+    private static final String WIFI_DOWNSTREAM = "WIFI";
+    private static final String USB_DOWNSTREAM = "USB";
+    private static final String BLUETOOTH_DOWNSTREAM = "BT";
     private static final boolean NOTIFY_DONE = true;
     private static final boolean NO_NOTIFY = false;
     // Id to update and cancel tethering notification. Must be unique within the tethering app.
-    private static final int NOTIFY_ID = 20191115;
+    private static final int ENABLE_NOTIFICATION_ID = 1000;
+    // Id to update and cancel restricted notification. Must be unique within the tethering app.
+    private static final int RESTRICTED_NOTIFICATION_ID = 1001;
     @VisibleForTesting
     static final int NO_ICON_ID = 0;
     @VisibleForTesting
@@ -65,14 +72,25 @@
     private final Context mContext;
     private final NotificationManager mNotificationManager;
     private final NotificationChannel mChannel;
-    // Downstream type is one of ConnectivityManager.TETHERING_* constants, 0 1 or 2.
-    // This value has to be made 1 2 and 4, and OR'd with the others.
+
     // WARNING : the constructor is called on a different thread. Thread safety therefore
     // relies on this value being initialized to 0, and not any other value. If you need
     // to change this, you will need to change the thread where the constructor is invoked,
     // or to introduce synchronization.
+    // Downstream type is one of ConnectivityManager.TETHERING_* constants, 0 1 or 2.
+    // This value has to be made 1 2 and 4, and OR'd with the others.
     private int mDownstreamTypesMask = DOWNSTREAM_NONE;
 
+    // WARNING : this value is not able to being initialized to 0 and must have volatile because
+    // telephony service is not guaranteed that is up before tethering service starts. If telephony
+    // is up later than tethering, TetheringNotificationUpdater will use incorrect and valid
+    // subscription id(0) to query resources. Therefore, initialized subscription id must be
+    // INVALID_SUBSCRIPTION_ID.
+    private volatile int mActiveDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+    @IntDef({ENABLE_NOTIFICATION_ID, RESTRICTED_NOTIFICATION_ID})
+    @interface NotificationId {}
+
     public TetheringNotificationUpdater(@NonNull final Context context) {
         mContext = context;
         mNotificationManager = (NotificationManager) context.createContextAsUser(UserHandle.ALL, 0)
@@ -88,19 +106,46 @@
     public void onDownstreamChanged(@IntRange(from = 0, to = 7) final int downstreamTypesMask) {
         if (mDownstreamTypesMask == downstreamTypesMask) return;
         mDownstreamTypesMask = downstreamTypesMask;
-        updateNotification();
+        updateEnableNotification();
     }
 
-    private void updateNotification() {
+    /** Called when active data subscription id changed */
+    public void onActiveDataSubscriptionIdChanged(final int subId) {
+        if (mActiveDataSubId == subId) return;
+        mActiveDataSubId = subId;
+        updateEnableNotification();
+    }
+
+    @VisibleForTesting
+    Resources getResourcesForSubId(@NonNull final Context c, final int subId) {
+        return SubscriptionManager.getResourcesForSubId(c, subId);
+    }
+
+    private void updateEnableNotification() {
         final boolean tetheringInactive = mDownstreamTypesMask <= DOWNSTREAM_NONE;
 
         if (tetheringInactive || setupNotification() == NO_NOTIFY) {
-            clearNotification();
+            clearNotification(ENABLE_NOTIFICATION_ID);
         }
     }
 
-    private void clearNotification() {
-        mNotificationManager.cancel(null /* tag */, NOTIFY_ID);
+    @VisibleForTesting
+    void tetheringRestrictionLifted() {
+        clearNotification(RESTRICTED_NOTIFICATION_ID);
+    }
+
+    private void clearNotification(@NotificationId final int id) {
+        mNotificationManager.cancel(null /* tag */, id);
+    }
+
+    @VisibleForTesting
+    void notifyTetheringDisabledByRestriction() {
+        final Resources res = getResourcesForSubId(mContext, mActiveDataSubId);
+        final String title = res.getString(R.string.disable_tether_notification_title);
+        final String message = res.getString(R.string.disable_tether_notification_message);
+
+        showNotification(R.drawable.stat_sys_tether_general, title, message,
+                RESTRICTED_NOTIFICATION_ID);
     }
 
     /**
@@ -110,16 +155,17 @@
      *
      * @return downstream types mask value.
      */
+    @VisibleForTesting
     @IntRange(from = 0, to = 7)
-    private int getDownstreamTypesMask(@NonNull final String types) {
+    int getDownstreamTypesMask(@NonNull final String types) {
         int downstreamTypesMask = DOWNSTREAM_NONE;
         final String[] downstreams = types.split("\\|");
         for (String downstream : downstreams) {
-            if ("USB".equals(downstream.trim())) {
+            if (USB_DOWNSTREAM.equals(downstream.trim())) {
                 downstreamTypesMask |= (1 << TETHERING_USB);
-            } else if ("WIFI".equals(downstream.trim())) {
+            } else if (WIFI_DOWNSTREAM.equals(downstream.trim())) {
                 downstreamTypesMask |= (1 << TETHERING_WIFI);
-            } else if ("BT".equals(downstream.trim())) {
+            } else if (BLUETOOTH_DOWNSTREAM.equals(downstream.trim())) {
                 downstreamTypesMask |= (1 << TETHERING_BLUETOOTH);
             }
         }
@@ -134,9 +180,8 @@
      *
      * @return {@link android.util.SparseArray} with downstream types and icon id info.
      */
-    @NonNull
-    private SparseArray<Integer> getIcons(@ArrayRes int id) {
-        final Resources res = mContext.getResources();
+    @VisibleForTesting
+    SparseArray<Integer> getIcons(@ArrayRes int id, @NonNull Resources res) {
         final String[] array = res.getStringArray(id);
         final SparseArray<Integer> icons = new SparseArray<>();
         for (String config : array) {
@@ -161,8 +206,9 @@
     }
 
     private boolean setupNotification() {
-        final Resources res = mContext.getResources();
-        final SparseArray<Integer> downstreamIcons = getIcons(R.array.tethering_notification_icons);
+        final Resources res = getResourcesForSubId(mContext, mActiveDataSubId);
+        final SparseArray<Integer> downstreamIcons =
+                getIcons(R.array.tethering_notification_icons, res);
 
         final int iconId = downstreamIcons.get(mDownstreamTypesMask, NO_ICON_ID);
         if (iconId == NO_ICON_ID) return NO_NOTIFY;
@@ -170,12 +216,12 @@
         final String title = res.getString(R.string.tethering_notification_title);
         final String message = res.getString(R.string.tethering_notification_message);
 
-        showNotification(iconId, title, message);
+        showNotification(iconId, title, message, ENABLE_NOTIFICATION_ID);
         return NOTIFY_DONE;
     }
 
     private void showNotification(@DrawableRes final int iconId, @NonNull final String title,
-            @NonNull final String message) {
+            @NonNull final String message, @NotificationId final int id) {
         final Intent intent = new Intent(Settings.ACTION_TETHER_SETTINGS);
         final PendingIntent pi = PendingIntent.getActivity(
                 mContext.createContextAsUser(UserHandle.CURRENT, 0),
@@ -193,6 +239,6 @@
                         .setContentIntent(pi)
                         .build();
 
-        mNotificationManager.notify(null /* tag */, NOTIFY_ID, notification);
+        mNotificationManager.notify(null /* tag */, id, notification);
     }
 }
diff --git a/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringNotificationUpdaterTest.kt b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringNotificationUpdaterTest.kt
new file mode 100644
index 0000000..b869491
--- /dev/null
+++ b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringNotificationUpdaterTest.kt
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2020 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.connectivity.tethering
+
+import android.app.Notification
+import android.app.NotificationManager
+import android.content.Context
+import android.content.res.Resources
+import android.net.ConnectivityManager.TETHERING_BLUETOOTH
+import android.net.ConnectivityManager.TETHERING_USB
+import android.net.ConnectivityManager.TETHERING_WIFI
+import android.os.UserHandle
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.util.test.BroadcastInterceptingContext
+import com.android.networkstack.tethering.R
+import com.android.server.connectivity.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+const val TEST_SUBID = 1
+const val WIFI_ICON_ID = 1
+const val USB_ICON_ID = 2
+const val BT_ICON_ID = 3
+const val GENERAL_ICON_ID = 4
+const val WIFI_MASK = 1 shl TETHERING_WIFI
+const val USB_MASK = 1 shl TETHERING_USB
+const val BT_MASK = 1 shl TETHERING_BLUETOOTH
+const val TITTLE = "Tethering active"
+const val MESSAGE = "Tap here to set up."
+const val TEST_TITTLE = "Hotspot active"
+const val TEST_MESSAGE = "Tap to set up hotspot."
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class TetheringNotificationUpdaterTest {
+    // lateinit used here for mocks as they need to be reinitialized between each test and the test
+    // should crash if they are used before being initialized.
+    @Mock private lateinit var mockContext: Context
+    @Mock private lateinit var notificationManager: NotificationManager
+    @Mock private lateinit var defaultResources: Resources
+    @Mock private lateinit var testResources: Resources
+
+    // lateinit for this class under test, as it should be reset to a different instance for every
+    // tests but should always be initialized before use (or the test should crash).
+    private lateinit var notificationUpdater: TetheringNotificationUpdater
+
+    private val ENABLE_ICON_CONFIGS = arrayOf(
+            "USB;android.test:drawable/usb", "BT;android.test:drawable/bluetooth",
+            "WIFI|BT;android.test:drawable/general", "WIFI|USB;android.test:drawable/general",
+            "USB|BT;android.test:drawable/general", "WIFI|USB|BT;android.test:drawable/general")
+
+    private inner class TestContext(c: Context) : BroadcastInterceptingContext(c) {
+        override fun createContextAsUser(user: UserHandle, flags: Int) =
+                if (user == UserHandle.ALL) mockContext else this
+    }
+
+    private inner class WrappedNotificationUpdater(c: Context) : TetheringNotificationUpdater(c) {
+        override fun getResourcesForSubId(context: Context, subId: Int) =
+                if (subId == TEST_SUBID) testResources else defaultResources
+    }
+
+    private fun setupResources() {
+        doReturn(ENABLE_ICON_CONFIGS).`when`(defaultResources)
+                .getStringArray(R.array.tethering_notification_icons)
+        doReturn(arrayOf("WIFI;android.test:drawable/wifi")).`when`(testResources)
+                .getStringArray(R.array.tethering_notification_icons)
+        doReturn(TITTLE).`when`(defaultResources).getString(R.string.tethering_notification_title)
+        doReturn(MESSAGE).`when`(defaultResources)
+                .getString(R.string.tethering_notification_message)
+        doReturn(TEST_TITTLE).`when`(testResources).getString(R.string.tethering_notification_title)
+        doReturn(TEST_MESSAGE).`when`(testResources)
+                .getString(R.string.tethering_notification_message)
+        doReturn(USB_ICON_ID).`when`(defaultResources)
+                .getIdentifier(eq("android.test:drawable/usb"), any(), any())
+        doReturn(BT_ICON_ID).`when`(defaultResources)
+                .getIdentifier(eq("android.test:drawable/bluetooth"), any(), any())
+        doReturn(GENERAL_ICON_ID).`when`(defaultResources)
+                .getIdentifier(eq("android.test:drawable/general"), any(), any())
+        doReturn(WIFI_ICON_ID).`when`(testResources)
+                .getIdentifier(eq("android.test:drawable/wifi"), any(), any())
+    }
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        val context = TestContext(InstrumentationRegistry.getInstrumentation().context)
+        doReturn(notificationManager).`when`(mockContext)
+                .getSystemService(Context.NOTIFICATION_SERVICE)
+        notificationUpdater = WrappedNotificationUpdater(context)
+        setupResources()
+    }
+
+    private fun Notification.title() = this.extras.getString(Notification.EXTRA_TITLE)
+    private fun Notification.text() = this.extras.getString(Notification.EXTRA_TEXT)
+
+    private fun verifyNotification(iconId: Int = 0, title: String = "", text: String = "") {
+        verify(notificationManager, never()).cancel(any(), anyInt())
+
+        val notificationCaptor = ArgumentCaptor.forClass(Notification::class.java)
+        verify(notificationManager, times(1))
+                .notify(any(), anyInt(), notificationCaptor.capture())
+
+        val notification = notificationCaptor.getValue()
+        assertEquals(iconId, notification.smallIcon.resId)
+        assertEquals(title, notification.title())
+        assertEquals(text, notification.text())
+
+        reset(notificationManager)
+    }
+
+    private fun verifyNoNotification() {
+        verify(notificationManager, times(1)).cancel(any(), anyInt())
+        verify(notificationManager, never()).notify(any(), anyInt(), any())
+
+        reset(notificationManager)
+    }
+
+    @Test
+    fun testNotificationWithDownstreamChanged() {
+        // Wifi downstream. No notification.
+        notificationUpdater.onDownstreamChanged(WIFI_MASK)
+        verifyNoNotification()
+
+        // Same downstream changed. Nothing happened.
+        notificationUpdater.onDownstreamChanged(WIFI_MASK)
+        verifyZeroInteractions(notificationManager)
+
+        // Wifi and usb downstreams. Show enable notification
+        notificationUpdater.onDownstreamChanged(WIFI_MASK or USB_MASK)
+        verifyNotification(GENERAL_ICON_ID, TITTLE, MESSAGE)
+
+        // Usb downstream. Still show enable notification.
+        notificationUpdater.onDownstreamChanged(USB_MASK)
+        verifyNotification(USB_ICON_ID, TITTLE, MESSAGE)
+
+        // No downstream. No notification.
+        notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE)
+        verifyNoNotification()
+    }
+
+    @Test
+    fun testNotificationWithActiveDataSubscriptionIdChanged() {
+        // Usb downstream. Showed enable notification with default resource.
+        notificationUpdater.onDownstreamChanged(USB_MASK)
+        verifyNotification(USB_ICON_ID, TITTLE, MESSAGE)
+
+        // Same subId changed. Nothing happened.
+        notificationUpdater.onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID)
+        verifyZeroInteractions(notificationManager)
+
+        // Set test sub id. Clear notification with test resource.
+        notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID)
+        verifyNoNotification()
+
+        // Wifi downstream. Show enable notification with test resource.
+        notificationUpdater.onDownstreamChanged(WIFI_MASK)
+        verifyNotification(WIFI_ICON_ID, TEST_TITTLE, TEST_MESSAGE)
+
+        // No downstream. No notification.
+        notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE)
+        verifyNoNotification()
+    }
+
+    private fun assertIconNumbers(number: Int, configs: Array<String?>) {
+        doReturn(configs).`when`(defaultResources)
+                .getStringArray(R.array.tethering_notification_icons)
+        assertEquals(number, notificationUpdater.getIcons(
+                R.array.tethering_notification_icons, defaultResources).size())
+    }
+
+    @Test
+    fun testGetIcons() {
+        assertIconNumbers(0, arrayOfNulls<String>(0))
+        assertIconNumbers(0, arrayOf(null, ""))
+        assertIconNumbers(3, arrayOf(
+                // These configurations are invalid with wrong strings or symbols.
+                ";", ",", "|", "|,;", "WIFI", "1;2", " U SB; ", "bt;", "WIFI;USB;BT", "WIFI|USB|BT",
+                "WIFI,BT,USB", " WIFI| |  | USB, test:drawable/test",
+                // This configuration is valid with two downstream types (USB, BT).
+                "USB|,,,,,|BT;drawable/test ",
+                // This configuration is valid with one downstream types (WIFI).
+                "     WIFI     ; android.test:drawable/xxx "))
+    }
+
+    @Test
+    fun testGetDownstreamTypesMask() {
+        assertEquals(DOWNSTREAM_NONE, notificationUpdater.getDownstreamTypesMask(""))
+        assertEquals(DOWNSTREAM_NONE, notificationUpdater.getDownstreamTypesMask("1"))
+        assertEquals(DOWNSTREAM_NONE, notificationUpdater.getDownstreamTypesMask("WIFI_P2P"))
+        assertEquals(DOWNSTREAM_NONE, notificationUpdater.getDownstreamTypesMask("usb"))
+        assertEquals(WIFI_MASK, notificationUpdater.getDownstreamTypesMask(" WIFI "))
+        assertEquals(USB_MASK, notificationUpdater.getDownstreamTypesMask("USB | B T"))
+        assertEquals(BT_MASK, notificationUpdater.getDownstreamTypesMask(" WIFI: | BT"))
+        assertEquals(WIFI_MASK or USB_MASK,
+                notificationUpdater.getDownstreamTypesMask("1|2|USB|WIFI|BLUETOOTH||"))
+    }
+
+    @Test
+    fun testSetupRestrictedNotification() {
+        val title = InstrumentationRegistry.getInstrumentation().context.resources
+                .getString(R.string.disable_tether_notification_title)
+        val message = InstrumentationRegistry.getInstrumentation().context.resources
+                .getString(R.string.disable_tether_notification_message)
+        val disallowTitle = "Tether function is disallowed"
+        val disallowMessage = "Please contact your admin"
+        doReturn(title).`when`(defaultResources)
+                .getString(R.string.disable_tether_notification_title)
+        doReturn(message).`when`(defaultResources)
+                .getString(R.string.disable_tether_notification_message)
+        doReturn(disallowTitle).`when`(testResources)
+                .getString(R.string.disable_tether_notification_title)
+        doReturn(disallowMessage).`when`(testResources)
+                .getString(R.string.disable_tether_notification_message)
+
+        // User restrictions on. Show restricted notification.
+        notificationUpdater.notifyTetheringDisabledByRestriction()
+        verifyNotification(R.drawable.stat_sys_tether_general, title, message)
+
+        // User restrictions off. Clear notification.
+        notificationUpdater.tetheringRestrictionLifted()
+        verifyNoNotification()
+
+        // Set test sub id. No notification.
+        notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID)
+        verifyNoNotification()
+
+        // User restrictions on again. Show restricted notification with test resource.
+        notificationUpdater.notifyTetheringDisabledByRestriction()
+        verifyNotification(R.drawable.stat_sys_tether_general, disallowTitle, disallowMessage)
+    }
+}
\ No newline at end of file
diff --git a/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index d983fae..0980514 100644
--- a/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -210,7 +210,6 @@
     private PhoneStateListener mPhoneStateListener;
     private InterfaceConfigurationParcel mInterfaceConfiguration;
 
-
     private class TestContext extends BroadcastInterceptingContext {
         TestContext(Context base) {
             super(base);
@@ -1073,13 +1072,15 @@
         when(mUserManager.getUserRestrictions()).thenReturn(newRestrictions);
 
         final Tethering.UserRestrictionActionListener ural =
-                new Tethering.UserRestrictionActionListener(mUserManager, mockTethering);
+                new Tethering.UserRestrictionActionListener(
+                        mUserManager, mockTethering, mNotificationUpdater);
         ural.mDisallowTethering = currentDisallow;
 
         ural.onUserRestrictionsChanged();
 
-        verify(mockTethering, times(expectedInteractionsWithShowNotification))
-                .untetherAll();
+        verify(mNotificationUpdater, times(expectedInteractionsWithShowNotification))
+                .notifyTetheringDisabledByRestriction();
+        verify(mockTethering, times(expectedInteractionsWithShowNotification)).untetherAll();
     }
 
     @Test
@@ -1087,7 +1088,7 @@
         final String[] emptyActiveIfacesList = new String[]{};
         final boolean currDisallow = false;
         final boolean nextDisallow = true;
-        final int expectedInteractionsWithShowNotification = 0;
+        final int expectedInteractionsWithShowNotification = 1;
 
         runUserRestrictionsChange(currDisallow, nextDisallow, emptyActiveIfacesList,
                 expectedInteractionsWithShowNotification);
@@ -1399,6 +1400,7 @@
         mPhoneStateListener.onActiveDataSubscriptionIdChanged(fakeSubId);
         final TetheringConfiguration newConfig = mTethering.getTetheringConfiguration();
         assertEquals(fakeSubId, newConfig.activeDataSubId);
+        verify(mNotificationUpdater, times(1)).onActiveDataSubscriptionIdChanged(eq(fakeSubId));
     }
 
     @Test