diff options
| author | 2020-03-09 08:10:55 +0000 | |
|---|---|---|
| committer | 2020-03-09 08:10:55 +0000 | |
| commit | 11fcab68b770e033f9c00d3a437c33ffb35a52ce (patch) | |
| tree | dc23ee8f0c4e1d2a7bd2fd3c399b4d39d9dcef48 | |
| parent | b3d7fe48adeddbef8a7c238b65cc37be49346978 (diff) | |
| parent | bff904017397a6fbd7a2c33515c5fe2dbcfa23ba (diff) | |
Merge "[TNU01] Add Tethering notification updater"
7 files changed, 333 insertions, 142 deletions
diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml index 379cd180d483..f825d6b07fd2 100644 --- a/packages/Tethering/res/values/config.xml +++ b/packages/Tethering/res/values/config.xml @@ -155,4 +155,49 @@ <!-- ComponentName of the service used to run no ui tether provisioning. --> <string translatable="false" name="config_wifi_tether_enable">com.android.settings/.wifi.tether.TetherService</string> + + <!-- Enable tethering notification --> + <!-- Icons for showing tether enable notification. + Each item should have two elements and be separated with ";". + + The first element is downstream types which is one of tethering. This element has to be + made by WIFI, USB, BT, and OR'd with the others. Use "|" to combine multiple downstream + types and use "," to separate each combinations. Such as + + USB|BT,WIFI|USB|BT + + The second element is icon for the item. This element has to be composed by + <package name>:drawable/<resource name>. Such as + + 1. com.android.networkstack.tethering:drawable/stat_sys_tether_general + 2. android:drawable/xxx + + So the entire string of each item would be + + USB|BT,WIFI|USB|BT;com.android.networkstack.tethering:drawable/stat_sys_tether_general + + NOTE: One config can be separated into two or more for readability. Such as + + WIFI|USB,WIFI|BT,USB|BT,WIFI|USB|BT;android:drawable/xxx + + can be separated into + + WIFI|USB;android:drawable/xxx + WIFI|BT;android:drawable/xxx + USB|BT;android:drawable/xxx + WIFI|USB|BT;android:drawable/xxx + + Notification will not show if the downstream type isn't listed in array. + Empty array means disable notifications. --> + <!-- In AOSP, hotspot is configured to no notification by default. Because status bar has showed + an icon on the right side already --> + <string-array translatable="false" name="tethering_notification_icons"> + <item>USB;com.android.networkstack.tethering:drawable/stat_sys_tether_usb</item> + <item>BT;com.android.networkstack.tethering:drawable/stat_sys_tether_bluetooth</item> + <item>WIFI|USB,WIFI|BT,USB|BT,WIFI|USB|BT;com.android.networkstack.tethering:drawable/stat_sys_tether_general</item> + </string-array> + <!-- String for tether enable notification title. --> + <string name="tethering_notification_title">@string/tethered_notification_title</string> + <!-- String for tether enable notification message. --> + <string name="tethering_notification_message">@string/tethered_notification_message</string> </resources> diff --git a/packages/Tethering/res/values/overlayable.xml b/packages/Tethering/res/values/overlayable.xml index fe025c7ac993..bbba3f30a292 100644 --- a/packages/Tethering/res/values/overlayable.xml +++ b/packages/Tethering/res/values/overlayable.xml @@ -16,6 +16,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android"> <overlayable name="TetheringConfig"> <policy type="product|system|vendor"> + <!-- Params from config.xml that can be overlaid --> <item type="array" name="config_tether_usb_regexs"/> <item type="array" name="config_tether_ncm_regexs" /> <item type="array" name="config_tether_wifi_regexs"/> @@ -31,6 +32,45 @@ <item type="string" name="config_mobile_hotspot_provision_response"/> <item type="integer" name="config_mobile_hotspot_provision_check_period"/> <item type="string" name="config_wifi_tether_enable"/> + <!-- Configuration values for TetheringNotificationUpdater --> + <!-- Icons for showing tether enable notification. + Each item should have two elements and be separated with ";". + + The first element is downstream types which is one of tethering. This element has to be + made by WIFI, USB, BT, and OR'd with the others. Use "|" to combine multiple downstream + types and use "," to separate each combinations. Such as + + USB|BT,WIFI|USB|BT + + The second element is icon for the item. This element has to be composed by + <package name>:drawable/<resource name>. Such as + + 1. com.android.networkstack.tethering:drawable/stat_sys_tether_general + 2. android:drawable/xxx + + So the entire string of each item would be + + USB|BT,WIFI|USB|BT;com.android.networkstack.tethering:drawable/stat_sys_tether_general + + NOTE: One config can be separated into two or more for readability. Such as + + WIFI|USB,WIFI|BT,USB|BT,WIFI|USB|BT;android:drawable/xxx + + can be separated into + + WIFI|USB;android:drawable/xxx + WIFI|BT;android:drawable/xxx + USB|BT;android:drawable/xxx + WIFI|USB|BT;android:drawable/xxx + + Notification will not show if the downstream type isn't listed in array. + Empty array means disable notifications. --> + <item type="array" name="tethering_notification_icons"/> + <!-- String for tether enable notification title. --> + <item type="string" name="tethering_notification_title"/> + <!-- String for tether enable notification message. --> + <item type="string" name="tethering_notification_message"/> + <!-- Params from config.xml that can be overlaid --> </policy> </overlayable> </resources> diff --git a/packages/Tethering/res/values/strings.xml b/packages/Tethering/res/values/strings.xml index 792bce9fc334..ba98a66ff7e0 100644 --- a/packages/Tethering/res/values/strings.xml +++ b/packages/Tethering/res/values/strings.xml @@ -15,19 +15,21 @@ --> <resources> <!-- Shown when the device is tethered --> - <!-- Strings for tethered notification title [CHAR LIMIT=200] --> + <!-- String for tethered notification title [CHAR LIMIT=200] --> <string name="tethered_notification_title">Tethering or hotspot active</string> - <!-- Strings for tethered notification message [CHAR LIMIT=200] --> + <!-- String for tethered notification message [CHAR LIMIT=200] --> <string name="tethered_notification_message">Tap to set up.</string> <!-- This notification is shown when tethering has been disabled on a user's device. The device is managed by the user's employer. Tethering can't be turned on unless the IT administrator allows it. The noun "admin" is another reference for "IT administrator." --> - <!-- Strings for tether disabling notification title [CHAR LIMIT=200] --> + <!-- String for tether disabling notification title [CHAR LIMIT=200] --> <string name="disable_tether_notification_title">Tethering is disabled</string> - <!-- Strings for tether disabling notification message [CHAR LIMIT=200] --> + <!-- String for tether disabling notification message [CHAR LIMIT=200] --> <string name="disable_tether_notification_message">Contact your admin for details</string> - <!-- Strings for tether notification channel name [CHAR LIMIT=200] --> + <!-- This string should be consistent with the "Hotspot & tethering" text in the "Network and + Internet" settings page. That is currently the tether_settings_title_all string. --> + <!-- String for tether notification channel name [CHAR LIMIT=200] --> <string name="notification_channel_tethering_status">Hotspot & tethering status</string> </resources>
\ No newline at end of file diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java index 68650f7e7b38..553901757397 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java @@ -59,10 +59,8 @@ import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED; import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; +import static com.android.server.connectivity.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE; + import android.app.usage.NetworkStatsManager; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothPan; @@ -72,7 +70,6 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.res.Resources; import android.hardware.usb.UsbManager; import android.net.ConnectivityManager; import android.net.EthernetManager; @@ -128,7 +125,6 @@ import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.MessageUtils; import com.android.internal.util.State; import com.android.internal.util.StateMachine; -import com.android.networkstack.tethering.R; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -224,14 +220,13 @@ public class Tethering { private final ActiveDataSubIdListener mActiveDataSubIdListener; private final ConnectedClientsTracker mConnectedClientsTracker; private final TetheringThreadExecutor mExecutor; + private final TetheringNotificationUpdater mNotificationUpdater; private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID; // All the usage of mTetheringEventCallback should run in the same thread. private ITetheringEventCallback mTetheringEventCallback = null; private volatile TetheringConfiguration mConfig; private InterfaceSet mCurrentUpstreamIfaceSet; - private Notification.Builder mTetheredNotificationBuilder; - private int mLastNotificationId; private boolean mRndisEnabled; // track the RNDIS function enabled state // True iff. WiFi tethering should be started when soft AP is ready. @@ -255,6 +250,7 @@ public class Tethering { mContext = mDeps.getContext(); mNetd = mDeps.getINetd(mContext); mLooper = mDeps.getTetheringLooper(); + mNotificationUpdater = mDeps.getNotificationUpdater(mContext); mPublicSync = new Object(); @@ -738,13 +734,10 @@ public class Tethering { final ArrayList<String> erroredList = new ArrayList<>(); final ArrayList<Integer> lastErrorList = new ArrayList<>(); - boolean wifiTethered = false; - boolean usbTethered = false; - boolean bluetoothTethered = false; - final TetheringConfiguration cfg = mConfig; mTetherStatesParcel = new TetherStatesParcel(); + int downstreamTypesMask = DOWNSTREAM_NONE; synchronized (mPublicSync) { for (int i = 0; i < mTetherStates.size(); i++) { TetherState tetherState = mTetherStates.valueAt(i); @@ -758,11 +751,11 @@ public class Tethering { localOnlyList.add(iface); } else if (tetherState.lastState == IpServer.STATE_TETHERED) { if (cfg.isUsb(iface)) { - usbTethered = true; + downstreamTypesMask |= (1 << TETHERING_USB); } else if (cfg.isWifi(iface)) { - wifiTethered = true; + downstreamTypesMask |= (1 << TETHERING_WIFI); } else if (cfg.isBluetooth(iface)) { - bluetoothTethered = true; + downstreamTypesMask |= (1 << TETHERING_BLUETOOTH); } tetherList.add(iface); } @@ -796,98 +789,7 @@ public class Tethering { "error", TextUtils.join(",", erroredList))); } - if (usbTethered) { - if (wifiTethered || bluetoothTethered) { - showTetheredNotification(R.drawable.stat_sys_tether_general); - } else { - showTetheredNotification(R.drawable.stat_sys_tether_usb); - } - } else if (wifiTethered) { - if (bluetoothTethered) { - showTetheredNotification(R.drawable.stat_sys_tether_general); - } else { - /* We now have a status bar icon for WifiTethering, so drop the notification */ - clearTetheredNotification(); - } - } else if (bluetoothTethered) { - showTetheredNotification(R.drawable.stat_sys_tether_bluetooth); - } else { - clearTetheredNotification(); - } - } - - private void showTetheredNotification(int id) { - showTetheredNotification(id, true); - } - - @VisibleForTesting - protected void showTetheredNotification(int id, boolean tetheringOn) { - NotificationManager notificationManager = - (NotificationManager) mContext.createContextAsUser(UserHandle.ALL, 0) - .getSystemService(Context.NOTIFICATION_SERVICE); - if (notificationManager == null) { - return; - } - final NotificationChannel channel = new NotificationChannel( - "TETHERING_STATUS", - mContext.getResources().getString(R.string.notification_channel_tethering_status), - NotificationManager.IMPORTANCE_LOW); - notificationManager.createNotificationChannel(channel); - - if (mLastNotificationId != 0) { - if (mLastNotificationId == id) { - return; - } - notificationManager.cancel(null, mLastNotificationId); - mLastNotificationId = 0; - } - - Intent intent = new Intent(); - intent.setClassName("com.android.settings", "com.android.settings.TetherSettings"); - intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); - - PendingIntent pi = PendingIntent.getActivity( - mContext.createContextAsUser(UserHandle.CURRENT, 0), 0, intent, 0, null); - - Resources r = mContext.getResources(); - final CharSequence title; - final CharSequence message; - - if (tetheringOn) { - title = r.getText(R.string.tethered_notification_title); - message = r.getText(R.string.tethered_notification_message); - } else { - title = r.getText(R.string.disable_tether_notification_title); - message = r.getText(R.string.disable_tether_notification_message); - } - - if (mTetheredNotificationBuilder == null) { - mTetheredNotificationBuilder = new Notification.Builder(mContext, channel.getId()); - mTetheredNotificationBuilder.setWhen(0) - .setOngoing(true) - .setColor(mContext.getColor( - android.R.color.system_notification_accent_color)) - .setVisibility(Notification.VISIBILITY_PUBLIC) - .setCategory(Notification.CATEGORY_STATUS); - } - mTetheredNotificationBuilder.setSmallIcon(id) - .setContentTitle(title) - .setContentText(message) - .setContentIntent(pi); - mLastNotificationId = id; - - notificationManager.notify(null, mLastNotificationId, mTetheredNotificationBuilder.build()); - } - - @VisibleForTesting - protected void clearTetheredNotification() { - NotificationManager notificationManager = - (NotificationManager) mContext.createContextAsUser(UserHandle.ALL, 0) - .getSystemService(Context.NOTIFICATION_SERVICE); - if (notificationManager != null && mLastNotificationId != 0) { - notificationManager.cancel(null, mLastNotificationId); - mLastNotificationId = 0; - } + mNotificationUpdater.onDownstreamChanged(downstreamTypesMask); } private class StateReceiver extends BroadcastReceiver { @@ -1081,12 +983,10 @@ public class Tethering { return; } - mWrapper.clearTetheredNotification(); + // TODO: Add user restrictions notification. final boolean isTetheringActiveOnDevice = (mWrapper.getTetheredIfaces().length != 0); if (newlyDisallowed && isTetheringActiveOnDevice) { - mWrapper.showTetheredNotification( - R.drawable.stat_sys_tether_general, false); mWrapper.untetherAll(); // TODO(b/148139325): send tetheringSupported on restriction change } diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java index e019c3aca26a..0330dad6a1ae 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java @@ -26,6 +26,8 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import androidx.annotation.NonNull; + import com.android.internal.util.StateMachine; import java.util.ArrayList; @@ -102,6 +104,13 @@ public abstract class TetheringDependencies { } /** + * Get a reference to the TetheringNotificationUpdater to be used by tethering. + */ + public TetheringNotificationUpdater getNotificationUpdater(@NonNull final Context ctx) { + return new TetheringNotificationUpdater(ctx); + } + + /** * Get tethering thread looper. */ public abstract Looper getTetheringLooper(); diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringNotificationUpdater.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringNotificationUpdater.java new file mode 100644 index 000000000000..b97f75268a3b --- /dev/null +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringNotificationUpdater.java @@ -0,0 +1,198 @@ +/* + * 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 static android.net.TetheringManager.TETHERING_BLUETOOTH; +import static android.net.TetheringManager.TETHERING_USB; +import static android.net.TetheringManager.TETHERING_WIFI; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.os.UserHandle; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; +import android.util.SparseArray; + +import androidx.annotation.ArrayRes; +import androidx.annotation.DrawableRes; +import androidx.annotation.IntRange; +import androidx.annotation.NonNull; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.networkstack.tethering.R; + +/** + * A class to display tethering-related notifications. + * + * <p>This class is not thread safe, it is intended to be used only from the tethering handler + * thread. However the constructor is an exception, as it is called on another thread ; + * therefore for thread safety all members of this class MUST either be final or initialized + * to their default value (0, false or null). + * + * @hide + */ +public class TetheringNotificationUpdater { + private static final String TAG = TetheringNotificationUpdater.class.getSimpleName(); + private static final String CHANNEL_ID = "TETHERING_STATUS"; + 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; + @VisibleForTesting + static final int NO_ICON_ID = 0; + @VisibleForTesting + static final int DOWNSTREAM_NONE = 0; + 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. + private int mDownstreamTypesMask = DOWNSTREAM_NONE; + + public TetheringNotificationUpdater(@NonNull final Context context) { + mContext = context; + mNotificationManager = (NotificationManager) context.createContextAsUser(UserHandle.ALL, 0) + .getSystemService(Context.NOTIFICATION_SERVICE); + mChannel = new NotificationChannel( + CHANNEL_ID, + context.getResources().getString(R.string.notification_channel_tethering_status), + NotificationManager.IMPORTANCE_LOW); + mNotificationManager.createNotificationChannel(mChannel); + } + + /** Called when downstream has changed */ + public void onDownstreamChanged(@IntRange(from = 0, to = 7) final int downstreamTypesMask) { + if (mDownstreamTypesMask == downstreamTypesMask) return; + mDownstreamTypesMask = downstreamTypesMask; + updateNotification(); + } + + private void updateNotification() { + final boolean tetheringInactive = mDownstreamTypesMask <= DOWNSTREAM_NONE; + + if (tetheringInactive || setupNotification() == NO_NOTIFY) { + clearNotification(); + } + } + + private void clearNotification() { + mNotificationManager.cancel(null /* tag */, NOTIFY_ID); + } + + /** + * Returns the downstream types mask which convert from given string. + * + * @param types This string has to be made by "WIFI", "USB", "BT", and OR'd with the others. + * + * @return downstream types mask value. + */ + @IntRange(from = 0, to = 7) + private int getDownstreamTypesMask(@NonNull final String types) { + int downstreamTypesMask = DOWNSTREAM_NONE; + final String[] downstreams = types.split("\\|"); + for (String downstream : downstreams) { + if ("USB".equals(downstream.trim())) { + downstreamTypesMask |= (1 << TETHERING_USB); + } else if ("WIFI".equals(downstream.trim())) { + downstreamTypesMask |= (1 << TETHERING_WIFI); + } else if ("BT".equals(downstream.trim())) { + downstreamTypesMask |= (1 << TETHERING_BLUETOOTH); + } + } + return downstreamTypesMask; + } + + /** + * Returns the icons {@link android.util.SparseArray} which get from given string-array resource + * id. + * + * @param id String-array resource id + * + * @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(); + final String[] array = res.getStringArray(id); + final SparseArray<Integer> icons = new SparseArray<>(); + for (String config : array) { + if (TextUtils.isEmpty(config)) continue; + + final String[] elements = config.split(";"); + if (elements.length != 2) { + Log.wtf(TAG, + "Unexpected format in Tethering notification configuration : " + config); + continue; + } + + final String[] types = elements[0].split(","); + for (String type : types) { + int mask = getDownstreamTypesMask(type); + if (mask == DOWNSTREAM_NONE) continue; + icons.put(mask, res.getIdentifier( + elements[1].trim(), null /* defType */, null /* defPackage */)); + } + } + return icons; + } + + private boolean setupNotification() { + final Resources res = mContext.getResources(); + final SparseArray<Integer> downstreamIcons = getIcons(R.array.tethering_notification_icons); + + final int iconId = downstreamIcons.get(mDownstreamTypesMask, NO_ICON_ID); + if (iconId == NO_ICON_ID) return NO_NOTIFY; + + final String title = res.getString(R.string.tethering_notification_title); + final String message = res.getString(R.string.tethering_notification_message); + + showNotification(iconId, title, message); + return NOTIFY_DONE; + } + + private void showNotification(@DrawableRes final int iconId, @NonNull final String title, + @NonNull final String message) { + final Intent intent = new Intent(Settings.ACTION_TETHER_SETTINGS); + final PendingIntent pi = PendingIntent.getActivity( + mContext.createContextAsUser(UserHandle.CURRENT, 0), + 0 /* requestCode */, intent, 0 /* flags */, null /* options */); + final Notification notification = + new Notification.Builder(mContext, mChannel.getId()) + .setSmallIcon(iconId) + .setContentTitle(title) + .setContentText(message) + .setOngoing(true) + .setColor(mContext.getColor( + android.R.color.system_notification_accent_color)) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setCategory(Notification.CATEGORY_STATUS) + .setContentIntent(pi) + .build(); + + mNotificationManager.notify(null /* tag */, NOTIFY_ID, notification); + } +} diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java index 1998cb3aa79c..2f7c88aec787 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java @@ -46,6 +46,8 @@ import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED; import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED; import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; +import static com.android.server.connectivity.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE; + import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -53,7 +55,6 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.notNull; -import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.any; @@ -188,6 +189,7 @@ public class TetheringTest { @Mock private NetworkRequest mNetworkRequest; @Mock private ConnectivityManager mCm; @Mock private EthernetManager mEm; + @Mock private TetheringNotificationUpdater mNotificationUpdater; private final MockIpServerDependencies mIpServerDependencies = spy(new MockIpServerDependencies()); @@ -207,6 +209,7 @@ public class TetheringTest { private PhoneStateListener mPhoneStateListener; private InterfaceConfigurationParcel mInterfaceConfiguration; + private class TestContext extends BroadcastInterceptingContext { TestContext(Context base) { super(base); @@ -249,11 +252,6 @@ public class TetheringTest { if (TelephonyManager.class.equals(serviceClass)) return Context.TELEPHONY_SERVICE; return super.getSystemServiceName(serviceClass); } - - @Override - public Context createContextAsUser(UserHandle user, int flags) { - return mContext; - } } public class MockIpServerDependencies extends IpServer.Dependencies { @@ -315,12 +313,10 @@ public class TetheringTest { public class MockTetheringDependencies extends TetheringDependencies { StateMachine mUpstreamNetworkMonitorMasterSM; ArrayList<IpServer> mIpv6CoordinatorNotifyList; - int mIsTetheringSupportedCalls; public void reset() { mUpstreamNetworkMonitorMasterSM = null; mIpv6CoordinatorNotifyList = null; - mIsTetheringSupportedCalls = 0; } @Override @@ -354,7 +350,6 @@ public class TetheringTest { @Override public boolean isTetheringSupported() { - mIsTetheringSupportedCalls++; return true; } @@ -384,6 +379,11 @@ public class TetheringTest { // TODO: add test for bluetooth tethering. return null; } + + @Override + public TetheringNotificationUpdater getNotificationUpdater(Context ctx) { + return mNotificationUpdater; + } } private static UpstreamNetworkState buildMobileUpstreamState(boolean withIPv4, @@ -472,7 +472,6 @@ public class TetheringTest { when(mOffloadHardwareInterface.getForwardedStats(any())).thenReturn(mForwardedStats); mServiceContext = new TestContext(mContext); - when(mContext.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(null); mContentResolver = new MockContentResolver(mServiceContext); mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); mIntents = new Vector<>(); @@ -605,7 +604,8 @@ public class TetheringTest { // it creates a IpServer and sends out a broadcast indicating that the // interface is "available". if (emulateInterfaceStatusChanged) { - assertEquals(1, mTetheringDependencies.mIsTetheringSupportedCalls); + // There is 1 IpServer state change event: STATE_AVAILABLE + verify(mNotificationUpdater, times(1)).onDownstreamChanged(DOWNSTREAM_NONE); verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER); verify(mWifiManager).updateInterfaceIpState( TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED); @@ -689,9 +689,8 @@ public class TetheringTest { verifyNoMoreInteractions(mWifiManager); verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY); verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks(); - // This will be called twice, one is on entering IpServer.STATE_AVAILABLE, - // and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY. - assertEquals(2, mTetheringDependencies.mIsTetheringSupportedCalls); + // There are 2 IpServer state change events: STATE_AVAILABLE -> STATE_LOCAL_ONLY + verify(mNotificationUpdater, times(2)).onDownstreamChanged(DOWNSTREAM_NONE); // Emulate externally-visible WifiManager effects, when hotspot mode // is being torn down. @@ -917,7 +916,8 @@ public class TetheringTest { sendWifiApStateChanged(WIFI_AP_STATE_ENABLED); mLooper.dispatchAll(); - assertEquals(1, mTetheringDependencies.mIsTetheringSupportedCalls); + // There is 1 IpServer state change event: STATE_AVAILABLE + verify(mNotificationUpdater, times(1)).onDownstreamChanged(DOWNSTREAM_NONE); verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER); verify(mWifiManager).updateInterfaceIpState( TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED); @@ -961,9 +961,9 @@ public class TetheringTest { // In tethering mode, in the default configuration, an explicit request // for a mobile network is also made. verify(mUpstreamNetworkMonitor, times(1)).registerMobileNetworkRequest(); - // This will be called twice, one is on entering IpServer.STATE_AVAILABLE, - // and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY. - assertEquals(2, mTetheringDependencies.mIsTetheringSupportedCalls); + // There are 2 IpServer state change events: STATE_AVAILABLE -> STATE_TETHERED + verify(mNotificationUpdater, times(1)).onDownstreamChanged(DOWNSTREAM_NONE); + verify(mNotificationUpdater, times(1)).onDownstreamChanged(eq(1 << TETHERING_WIFI)); ///// // We do not currently emulate any upstream being found. @@ -1034,9 +1034,10 @@ public class TetheringTest { TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED); verify(mWifiManager).updateInterfaceIpState( TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED); - // There are 3 state change event: - // AVAILABLE -> STATE_TETHERED -> STATE_AVAILABLE. - assertEquals(3, mTetheringDependencies.mIsTetheringSupportedCalls); + // There are 3 IpServer state change event: + // STATE_AVAILABLE -> STATE_TETHERED -> STATE_AVAILABLE. + verify(mNotificationUpdater, times(2)).onDownstreamChanged(DOWNSTREAM_NONE); + verify(mNotificationUpdater, times(1)).onDownstreamChanged(eq(1 << TETHERING_WIFI)); verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER); // This is called, but will throw. verify(mNetd, times(1)).ipfwdEnableForwarding(TETHERING_NAME); @@ -1071,9 +1072,6 @@ public class TetheringTest { ural.onUserRestrictionsChanged(); verify(mockTethering, times(expectedInteractionsWithShowNotification)) - .showTetheredNotification(anyInt(), eq(false)); - - verify(mockTethering, times(expectedInteractionsWithShowNotification)) .untetherAll(); } @@ -1429,9 +1427,8 @@ public class TetheringTest { verifyNoMoreInteractions(mNetd); verifyTetheringBroadcast(TEST_P2P_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY); verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks(); - // This will be called twice, one is on entering IpServer.STATE_AVAILABLE, - // and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY. - assertEquals(2, mTetheringDependencies.mIsTetheringSupportedCalls); + // There are 2 IpServer state change events: STATE_AVAILABLE -> STATE_LOCAL_ONLY + verify(mNotificationUpdater, times(2)).onDownstreamChanged(DOWNSTREAM_NONE); assertEquals(TETHER_ERROR_NO_ERROR, mTethering.getLastTetherError(TEST_P2P_IFNAME)); |