[TNU01] Add Tethering notification updater
There are lots of Carrier/OEM requests for tethering notification customization. So add a new tethering notification updater class which can let OEM customize the behavior they wanted. Bug: 122085773 Test: atest TetheringTests Change-Id: I7faacde7ac84e93ea0dfe03dd33d2cc41c589225 Merged-In: I7faacde7ac84e93ea0dfe03dd33d2cc41c589225 (cherry picked from aosp/1137358)
This commit is contained in:
committed by
Paul Hu
parent
ab74d2b865
commit
9cb35621a6
@@ -157,4 +157,49 @@
|
|||||||
|
|
||||||
<!-- ComponentName of the service used to run no ui tether provisioning. -->
|
<!-- 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>
|
<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>
|
</resources>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<overlayable name="TetheringConfig">
|
<overlayable name="TetheringConfig">
|
||||||
<policy type="product|system|vendor">
|
<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_usb_regexs"/>
|
||||||
<item type="array" name="config_tether_ncm_regexs" />
|
<item type="array" name="config_tether_ncm_regexs" />
|
||||||
<item type="array" name="config_tether_wifi_regexs"/>
|
<item type="array" name="config_tether_wifi_regexs"/>
|
||||||
@@ -31,6 +32,45 @@
|
|||||||
<item type="string" name="config_mobile_hotspot_provision_response"/>
|
<item type="string" name="config_mobile_hotspot_provision_response"/>
|
||||||
<item type="integer" name="config_mobile_hotspot_provision_check_period"/>
|
<item type="integer" name="config_mobile_hotspot_provision_check_period"/>
|
||||||
<item type="string" name="config_wifi_tether_enable"/>
|
<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>
|
</policy>
|
||||||
</overlayable>
|
</overlayable>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -15,19 +15,21 @@
|
|||||||
-->
|
-->
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Shown when the device is tethered -->
|
<!-- 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>
|
<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>
|
<string name="tethered_notification_message">Tap to set up.</string>
|
||||||
|
|
||||||
<!-- This notification is shown when tethering has been disabled on a user's device.
|
<!-- 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
|
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." -->
|
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>
|
<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>
|
<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>
|
<string name="notification_channel_tethering_status">Hotspot & tethering status</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -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.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
|
||||||
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
|
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
|
||||||
|
|
||||||
import android.app.Notification;
|
import static com.android.server.connectivity.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE;
|
||||||
import android.app.NotificationChannel;
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.app.usage.NetworkStatsManager;
|
import android.app.usage.NetworkStatsManager;
|
||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.bluetooth.BluetoothPan;
|
import android.bluetooth.BluetoothPan;
|
||||||
@@ -72,7 +70,6 @@ import android.content.BroadcastReceiver;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.hardware.usb.UsbManager;
|
import android.hardware.usb.UsbManager;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.EthernetManager;
|
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.MessageUtils;
|
||||||
import com.android.internal.util.State;
|
import com.android.internal.util.State;
|
||||||
import com.android.internal.util.StateMachine;
|
import com.android.internal.util.StateMachine;
|
||||||
import com.android.networkstack.tethering.R;
|
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
@@ -224,14 +220,13 @@ public class Tethering {
|
|||||||
private final ActiveDataSubIdListener mActiveDataSubIdListener;
|
private final ActiveDataSubIdListener mActiveDataSubIdListener;
|
||||||
private final ConnectedClientsTracker mConnectedClientsTracker;
|
private final ConnectedClientsTracker mConnectedClientsTracker;
|
||||||
private final TetheringThreadExecutor mExecutor;
|
private final TetheringThreadExecutor mExecutor;
|
||||||
|
private final TetheringNotificationUpdater mNotificationUpdater;
|
||||||
private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
|
private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
|
||||||
// All the usage of mTetheringEventCallback should run in the same thread.
|
// All the usage of mTetheringEventCallback should run in the same thread.
|
||||||
private ITetheringEventCallback mTetheringEventCallback = null;
|
private ITetheringEventCallback mTetheringEventCallback = null;
|
||||||
|
|
||||||
private volatile TetheringConfiguration mConfig;
|
private volatile TetheringConfiguration mConfig;
|
||||||
private InterfaceSet mCurrentUpstreamIfaceSet;
|
private InterfaceSet mCurrentUpstreamIfaceSet;
|
||||||
private Notification.Builder mTetheredNotificationBuilder;
|
|
||||||
private int mLastNotificationId;
|
|
||||||
|
|
||||||
private boolean mRndisEnabled; // track the RNDIS function enabled state
|
private boolean mRndisEnabled; // track the RNDIS function enabled state
|
||||||
// True iff. WiFi tethering should be started when soft AP is ready.
|
// True iff. WiFi tethering should be started when soft AP is ready.
|
||||||
@@ -255,6 +250,7 @@ public class Tethering {
|
|||||||
mContext = mDeps.getContext();
|
mContext = mDeps.getContext();
|
||||||
mNetd = mDeps.getINetd(mContext);
|
mNetd = mDeps.getINetd(mContext);
|
||||||
mLooper = mDeps.getTetheringLooper();
|
mLooper = mDeps.getTetheringLooper();
|
||||||
|
mNotificationUpdater = mDeps.getNotificationUpdater(mContext);
|
||||||
|
|
||||||
mPublicSync = new Object();
|
mPublicSync = new Object();
|
||||||
|
|
||||||
@@ -738,13 +734,10 @@ public class Tethering {
|
|||||||
final ArrayList<String> erroredList = new ArrayList<>();
|
final ArrayList<String> erroredList = new ArrayList<>();
|
||||||
final ArrayList<Integer> lastErrorList = new ArrayList<>();
|
final ArrayList<Integer> lastErrorList = new ArrayList<>();
|
||||||
|
|
||||||
boolean wifiTethered = false;
|
|
||||||
boolean usbTethered = false;
|
|
||||||
boolean bluetoothTethered = false;
|
|
||||||
|
|
||||||
final TetheringConfiguration cfg = mConfig;
|
final TetheringConfiguration cfg = mConfig;
|
||||||
mTetherStatesParcel = new TetherStatesParcel();
|
mTetherStatesParcel = new TetherStatesParcel();
|
||||||
|
|
||||||
|
int downstreamTypesMask = DOWNSTREAM_NONE;
|
||||||
synchronized (mPublicSync) {
|
synchronized (mPublicSync) {
|
||||||
for (int i = 0; i < mTetherStates.size(); i++) {
|
for (int i = 0; i < mTetherStates.size(); i++) {
|
||||||
TetherState tetherState = mTetherStates.valueAt(i);
|
TetherState tetherState = mTetherStates.valueAt(i);
|
||||||
@@ -758,11 +751,11 @@ public class Tethering {
|
|||||||
localOnlyList.add(iface);
|
localOnlyList.add(iface);
|
||||||
} else if (tetherState.lastState == IpServer.STATE_TETHERED) {
|
} else if (tetherState.lastState == IpServer.STATE_TETHERED) {
|
||||||
if (cfg.isUsb(iface)) {
|
if (cfg.isUsb(iface)) {
|
||||||
usbTethered = true;
|
downstreamTypesMask |= (1 << TETHERING_USB);
|
||||||
} else if (cfg.isWifi(iface)) {
|
} else if (cfg.isWifi(iface)) {
|
||||||
wifiTethered = true;
|
downstreamTypesMask |= (1 << TETHERING_WIFI);
|
||||||
} else if (cfg.isBluetooth(iface)) {
|
} else if (cfg.isBluetooth(iface)) {
|
||||||
bluetoothTethered = true;
|
downstreamTypesMask |= (1 << TETHERING_BLUETOOTH);
|
||||||
}
|
}
|
||||||
tetherList.add(iface);
|
tetherList.add(iface);
|
||||||
}
|
}
|
||||||
@@ -796,98 +789,7 @@ public class Tethering {
|
|||||||
"error", TextUtils.join(",", erroredList)));
|
"error", TextUtils.join(",", erroredList)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (usbTethered) {
|
mNotificationUpdater.onDownstreamChanged(downstreamTypesMask);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class StateReceiver extends BroadcastReceiver {
|
private class StateReceiver extends BroadcastReceiver {
|
||||||
@@ -1081,12 +983,10 @@ public class Tethering {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mWrapper.clearTetheredNotification();
|
// TODO: Add user restrictions notification.
|
||||||
final boolean isTetheringActiveOnDevice = (mWrapper.getTetheredIfaces().length != 0);
|
final boolean isTetheringActiveOnDevice = (mWrapper.getTetheredIfaces().length != 0);
|
||||||
|
|
||||||
if (newlyDisallowed && isTetheringActiveOnDevice) {
|
if (newlyDisallowed && isTetheringActiveOnDevice) {
|
||||||
mWrapper.showTetheredNotification(
|
|
||||||
R.drawable.stat_sys_tether_general, false);
|
|
||||||
mWrapper.untetherAll();
|
mWrapper.untetherAll();
|
||||||
// TODO(b/148139325): send tetheringSupported on restriction change
|
// TODO(b/148139325): send tetheringSupported on restriction change
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import android.os.Handler;
|
|||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.android.internal.util.StateMachine;
|
import com.android.internal.util.StateMachine;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -101,6 +103,13 @@ public abstract class TetheringDependencies {
|
|||||||
(IBinder) context.getSystemService(Context.NETD_SERVICE));
|
(IBinder) context.getSystemService(Context.NETD_SERVICE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
* Get tethering thread looper.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
|
||||||
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
|
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.assertArrayEquals;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
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.junit.Assert.fail;
|
||||||
import static org.mockito.ArgumentMatchers.argThat;
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
import static org.mockito.ArgumentMatchers.notNull;
|
import static org.mockito.ArgumentMatchers.notNull;
|
||||||
import static org.mockito.Matchers.anyInt;
|
|
||||||
import static org.mockito.Matchers.anyString;
|
import static org.mockito.Matchers.anyString;
|
||||||
import static org.mockito.Matchers.eq;
|
import static org.mockito.Matchers.eq;
|
||||||
import static org.mockito.Mockito.any;
|
import static org.mockito.Mockito.any;
|
||||||
@@ -188,6 +189,7 @@ public class TetheringTest {
|
|||||||
@Mock private NetworkRequest mNetworkRequest;
|
@Mock private NetworkRequest mNetworkRequest;
|
||||||
@Mock private ConnectivityManager mCm;
|
@Mock private ConnectivityManager mCm;
|
||||||
@Mock private EthernetManager mEm;
|
@Mock private EthernetManager mEm;
|
||||||
|
@Mock private TetheringNotificationUpdater mNotificationUpdater;
|
||||||
|
|
||||||
private final MockIpServerDependencies mIpServerDependencies =
|
private final MockIpServerDependencies mIpServerDependencies =
|
||||||
spy(new MockIpServerDependencies());
|
spy(new MockIpServerDependencies());
|
||||||
@@ -207,6 +209,7 @@ public class TetheringTest {
|
|||||||
private PhoneStateListener mPhoneStateListener;
|
private PhoneStateListener mPhoneStateListener;
|
||||||
private InterfaceConfigurationParcel mInterfaceConfiguration;
|
private InterfaceConfigurationParcel mInterfaceConfiguration;
|
||||||
|
|
||||||
|
|
||||||
private class TestContext extends BroadcastInterceptingContext {
|
private class TestContext extends BroadcastInterceptingContext {
|
||||||
TestContext(Context base) {
|
TestContext(Context base) {
|
||||||
super(base);
|
super(base);
|
||||||
@@ -249,11 +252,6 @@ public class TetheringTest {
|
|||||||
if (TelephonyManager.class.equals(serviceClass)) return Context.TELEPHONY_SERVICE;
|
if (TelephonyManager.class.equals(serviceClass)) return Context.TELEPHONY_SERVICE;
|
||||||
return super.getSystemServiceName(serviceClass);
|
return super.getSystemServiceName(serviceClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Context createContextAsUser(UserHandle user, int flags) {
|
|
||||||
return mContext;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MockIpServerDependencies extends IpServer.Dependencies {
|
public class MockIpServerDependencies extends IpServer.Dependencies {
|
||||||
@@ -315,12 +313,10 @@ public class TetheringTest {
|
|||||||
public class MockTetheringDependencies extends TetheringDependencies {
|
public class MockTetheringDependencies extends TetheringDependencies {
|
||||||
StateMachine mUpstreamNetworkMonitorMasterSM;
|
StateMachine mUpstreamNetworkMonitorMasterSM;
|
||||||
ArrayList<IpServer> mIpv6CoordinatorNotifyList;
|
ArrayList<IpServer> mIpv6CoordinatorNotifyList;
|
||||||
int mIsTetheringSupportedCalls;
|
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
mUpstreamNetworkMonitorMasterSM = null;
|
mUpstreamNetworkMonitorMasterSM = null;
|
||||||
mIpv6CoordinatorNotifyList = null;
|
mIpv6CoordinatorNotifyList = null;
|
||||||
mIsTetheringSupportedCalls = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -354,7 +350,6 @@ public class TetheringTest {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isTetheringSupported() {
|
public boolean isTetheringSupported() {
|
||||||
mIsTetheringSupportedCalls++;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,6 +379,11 @@ public class TetheringTest {
|
|||||||
// TODO: add test for bluetooth tethering.
|
// TODO: add test for bluetooth tethering.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TetheringNotificationUpdater getNotificationUpdater(Context ctx) {
|
||||||
|
return mNotificationUpdater;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UpstreamNetworkState buildMobileUpstreamState(boolean withIPv4,
|
private static UpstreamNetworkState buildMobileUpstreamState(boolean withIPv4,
|
||||||
@@ -472,7 +472,6 @@ public class TetheringTest {
|
|||||||
when(mOffloadHardwareInterface.getForwardedStats(any())).thenReturn(mForwardedStats);
|
when(mOffloadHardwareInterface.getForwardedStats(any())).thenReturn(mForwardedStats);
|
||||||
|
|
||||||
mServiceContext = new TestContext(mContext);
|
mServiceContext = new TestContext(mContext);
|
||||||
when(mContext.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(null);
|
|
||||||
mContentResolver = new MockContentResolver(mServiceContext);
|
mContentResolver = new MockContentResolver(mServiceContext);
|
||||||
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
|
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
|
||||||
mIntents = new Vector<>();
|
mIntents = new Vector<>();
|
||||||
@@ -605,7 +604,8 @@ public class TetheringTest {
|
|||||||
// it creates a IpServer and sends out a broadcast indicating that the
|
// it creates a IpServer and sends out a broadcast indicating that the
|
||||||
// interface is "available".
|
// interface is "available".
|
||||||
if (emulateInterfaceStatusChanged) {
|
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);
|
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
|
||||||
verify(mWifiManager).updateInterfaceIpState(
|
verify(mWifiManager).updateInterfaceIpState(
|
||||||
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
|
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
|
||||||
@@ -689,9 +689,8 @@ public class TetheringTest {
|
|||||||
verifyNoMoreInteractions(mWifiManager);
|
verifyNoMoreInteractions(mWifiManager);
|
||||||
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY);
|
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY);
|
||||||
verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks();
|
verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks();
|
||||||
// This will be called twice, one is on entering IpServer.STATE_AVAILABLE,
|
// There are 2 IpServer state change events: STATE_AVAILABLE -> STATE_LOCAL_ONLY
|
||||||
// and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY.
|
verify(mNotificationUpdater, times(2)).onDownstreamChanged(DOWNSTREAM_NONE);
|
||||||
assertEquals(2, mTetheringDependencies.mIsTetheringSupportedCalls);
|
|
||||||
|
|
||||||
// Emulate externally-visible WifiManager effects, when hotspot mode
|
// Emulate externally-visible WifiManager effects, when hotspot mode
|
||||||
// is being torn down.
|
// is being torn down.
|
||||||
@@ -917,7 +916,8 @@ public class TetheringTest {
|
|||||||
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED);
|
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED);
|
||||||
mLooper.dispatchAll();
|
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);
|
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
|
||||||
verify(mWifiManager).updateInterfaceIpState(
|
verify(mWifiManager).updateInterfaceIpState(
|
||||||
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
|
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
|
// In tethering mode, in the default configuration, an explicit request
|
||||||
// for a mobile network is also made.
|
// for a mobile network is also made.
|
||||||
verify(mUpstreamNetworkMonitor, times(1)).registerMobileNetworkRequest();
|
verify(mUpstreamNetworkMonitor, times(1)).registerMobileNetworkRequest();
|
||||||
// This will be called twice, one is on entering IpServer.STATE_AVAILABLE,
|
// There are 2 IpServer state change events: STATE_AVAILABLE -> STATE_TETHERED
|
||||||
// and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY.
|
verify(mNotificationUpdater, times(1)).onDownstreamChanged(DOWNSTREAM_NONE);
|
||||||
assertEquals(2, mTetheringDependencies.mIsTetheringSupportedCalls);
|
verify(mNotificationUpdater, times(1)).onDownstreamChanged(eq(1 << TETHERING_WIFI));
|
||||||
|
|
||||||
/////
|
/////
|
||||||
// We do not currently emulate any upstream being found.
|
// We do not currently emulate any upstream being found.
|
||||||
@@ -1034,9 +1034,10 @@ public class TetheringTest {
|
|||||||
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
|
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
|
||||||
verify(mWifiManager).updateInterfaceIpState(
|
verify(mWifiManager).updateInterfaceIpState(
|
||||||
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED);
|
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_TETHERED);
|
||||||
// There are 3 state change event:
|
// There are 3 IpServer state change event:
|
||||||
// AVAILABLE -> STATE_TETHERED -> STATE_AVAILABLE.
|
// STATE_AVAILABLE -> STATE_TETHERED -> STATE_AVAILABLE.
|
||||||
assertEquals(3, mTetheringDependencies.mIsTetheringSupportedCalls);
|
verify(mNotificationUpdater, times(2)).onDownstreamChanged(DOWNSTREAM_NONE);
|
||||||
|
verify(mNotificationUpdater, times(1)).onDownstreamChanged(eq(1 << TETHERING_WIFI));
|
||||||
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
|
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
|
||||||
// This is called, but will throw.
|
// This is called, but will throw.
|
||||||
verify(mNetd, times(1)).ipfwdEnableForwarding(TETHERING_NAME);
|
verify(mNetd, times(1)).ipfwdEnableForwarding(TETHERING_NAME);
|
||||||
@@ -1070,9 +1071,6 @@ public class TetheringTest {
|
|||||||
|
|
||||||
ural.onUserRestrictionsChanged();
|
ural.onUserRestrictionsChanged();
|
||||||
|
|
||||||
verify(mockTethering, times(expectedInteractionsWithShowNotification))
|
|
||||||
.showTetheredNotification(anyInt(), eq(false));
|
|
||||||
|
|
||||||
verify(mockTethering, times(expectedInteractionsWithShowNotification))
|
verify(mockTethering, times(expectedInteractionsWithShowNotification))
|
||||||
.untetherAll();
|
.untetherAll();
|
||||||
}
|
}
|
||||||
@@ -1429,9 +1427,8 @@ public class TetheringTest {
|
|||||||
verifyNoMoreInteractions(mNetd);
|
verifyNoMoreInteractions(mNetd);
|
||||||
verifyTetheringBroadcast(TEST_P2P_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY);
|
verifyTetheringBroadcast(TEST_P2P_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY);
|
||||||
verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks();
|
verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks();
|
||||||
// This will be called twice, one is on entering IpServer.STATE_AVAILABLE,
|
// There are 2 IpServer state change events: STATE_AVAILABLE -> STATE_LOCAL_ONLY
|
||||||
// and another one is on IpServer.STATE_TETHERED/IpServer.STATE_LOCAL_ONLY.
|
verify(mNotificationUpdater, times(2)).onDownstreamChanged(DOWNSTREAM_NONE);
|
||||||
assertEquals(2, mTetheringDependencies.mIsTetheringSupportedCalls);
|
|
||||||
|
|
||||||
assertEquals(TETHER_ERROR_NO_ERROR, mTethering.getLastTetherError(TEST_P2P_IFNAME));
|
assertEquals(TETHER_ERROR_NO_ERROR, mTethering.getLastTetherError(TEST_P2P_IFNAME));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user