Merge "[TNU05] Add no upstream notification"
This commit is contained in:
@@ -34,11 +34,14 @@
|
||||
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
|
||||
<uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
|
||||
<uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
|
||||
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
|
||||
<uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
|
||||
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
|
||||
|
||||
<protected-broadcast android:name="com.android.server.connectivity.tethering.DISABLE_TETHERING" />
|
||||
|
||||
<application
|
||||
android:process="com.android.networkstack.process"
|
||||
android:extractNativeLibs="false"
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
<resources>
|
||||
<!-- String for no upstream notification title [CHAR LIMIT=200] -->
|
||||
<string name="no_upstream_notification_title">Tethering has no internet</string>
|
||||
<!-- String for no upstream notification title [CHAR LIMIT=200] -->
|
||||
<string name="no_upstream_notification_message">Devices can\u2019t connect</string>
|
||||
<!-- String for no upstream notification disable button [CHAR LIMIT=200] -->
|
||||
<string name="no_upstream_notification_disable_button">Turn off tethering</string>
|
||||
|
||||
<!-- String for cellular roaming notification title [CHAR LIMIT=200] -->
|
||||
<string name="upstream_roaming_notification_title">Hotspot or tethering is on</string>
|
||||
<!-- String for cellular roaming notification message [CHAR LIMIT=500] -->
|
||||
<string name="upstream_roaming_notification_message">Additional charges may apply while roaming</string>
|
||||
<!-- String for cellular roaming notification continue button [CHAR LIMIT=200] -->
|
||||
<string name="upstream_roaming_notification_continue_button">Continue</string>
|
||||
</resources>
|
||||
20
Tethering/res/values-mcc310-mnc004/config.xml
Normal file
20
Tethering/res/values-mcc310-mnc004/config.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
<resources>
|
||||
<!-- Delay(millisecond) to show no upstream notification after there's no Backhaul. Set delay to
|
||||
"0" for disable this feature. -->
|
||||
<integer name="delay_to_show_no_upstream_after_no_backhaul">5000</integer>
|
||||
</resources>
|
||||
20
Tethering/res/values-mcc311-mnc480/config.xml
Normal file
20
Tethering/res/values-mcc311-mnc480/config.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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.
|
||||
-->
|
||||
<resources>
|
||||
<!-- Delay(millisecond) to show no upstream notification after there's no Backhaul. Set delay to
|
||||
"0" for disable this feature. -->
|
||||
<integer name="delay_to_show_no_upstream_after_no_backhaul">5000</integer>
|
||||
</resources>
|
||||
@@ -200,4 +200,10 @@
|
||||
<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>
|
||||
|
||||
<!-- No upstream notification is shown when there is a downstream but no upstream that is able
|
||||
to do the tethering. -->
|
||||
<!-- Delay(millisecond) to show no upstream notification after there's no Backhaul. Set delay to
|
||||
"-1" for disable this feature. -->
|
||||
<integer name="delay_to_show_no_upstream_after_no_backhaul">-1</integer>
|
||||
</resources>
|
||||
|
||||
@@ -257,7 +257,7 @@ public class Tethering {
|
||||
mContext = mDeps.getContext();
|
||||
mNetd = mDeps.getINetd(mContext);
|
||||
mLooper = mDeps.getTetheringLooper();
|
||||
mNotificationUpdater = mDeps.getNotificationUpdater(mContext);
|
||||
mNotificationUpdater = mDeps.getNotificationUpdater(mContext, mLooper);
|
||||
|
||||
mPublicSync = new Object();
|
||||
|
||||
@@ -337,6 +337,11 @@ public class Tethering {
|
||||
filter.addAction(ACTION_RESTRICT_BACKGROUND_CHANGED);
|
||||
mContext.registerReceiver(mStateReceiver, filter, null, mHandler);
|
||||
|
||||
final IntentFilter noUpstreamFilter = new IntentFilter();
|
||||
noUpstreamFilter.addAction(TetheringNotificationUpdater.ACTION_DISABLE_TETHERING);
|
||||
mContext.registerReceiver(
|
||||
mStateReceiver, noUpstreamFilter, PERMISSION_MAINLINE_NETWORK_STACK, mHandler);
|
||||
|
||||
final WifiManager wifiManager = getWifiManager();
|
||||
if (wifiManager != null) {
|
||||
wifiManager.registerSoftApCallback(mExecutor, new TetheringSoftApCallback());
|
||||
@@ -855,6 +860,8 @@ public class Tethering {
|
||||
} else if (action.equals(ACTION_RESTRICT_BACKGROUND_CHANGED)) {
|
||||
mLog.log("OBSERVED data saver changed");
|
||||
handleDataSaverChanged();
|
||||
} else if (action.equals(TetheringNotificationUpdater.ACTION_DISABLE_TETHERING)) {
|
||||
untetherAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2013,6 +2020,7 @@ public class Tethering {
|
||||
} finally {
|
||||
mTetheringEventCallbacks.finishBroadcast();
|
||||
}
|
||||
mNotificationUpdater.onUpstreamNetworkChanged(network);
|
||||
}
|
||||
|
||||
private void reportConfigurationChanged(TetheringConfigurationParcel config) {
|
||||
|
||||
@@ -106,8 +106,9 @@ 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);
|
||||
public TetheringNotificationUpdater getNotificationUpdater(@NonNull final Context ctx,
|
||||
@NonNull final Looper looper) {
|
||||
return new TetheringNotificationUpdater(ctx, looper);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,18 +19,25 @@ package com.android.networkstack.tethering;
|
||||
import static android.net.TetheringManager.TETHERING_BLUETOOTH;
|
||||
import static android.net.TetheringManager.TETHERING_USB;
|
||||
import static android.net.TetheringManager.TETHERING_WIFI;
|
||||
import static android.text.TextUtils.isEmpty;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.Notification.Action;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.net.Network;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.telephony.SubscriptionManager;
|
||||
import android.text.TextUtils;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
@@ -39,9 +46,13 @@ import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.IntRange;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A class to display tethering-related notifications.
|
||||
*
|
||||
@@ -58,12 +69,22 @@ public class TetheringNotificationUpdater {
|
||||
private static final String WIFI_DOWNSTREAM = "WIFI";
|
||||
private static final String USB_DOWNSTREAM = "USB";
|
||||
private static final String BLUETOOTH_DOWNSTREAM = "BT";
|
||||
@VisibleForTesting
|
||||
static final String ACTION_DISABLE_TETHERING =
|
||||
"com.android.server.connectivity.tethering.DISABLE_TETHERING";
|
||||
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 ENABLE_NOTIFICATION_ID = 1000;
|
||||
@VisibleForTesting
|
||||
static final int EVENT_SHOW_NO_UPSTREAM = 1;
|
||||
// Id to update and cancel enable notification. Must be unique within the tethering app.
|
||||
@VisibleForTesting
|
||||
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 RESTRICTED_NOTIFICATION_ID = 1001;
|
||||
// Id to update and cancel no upstream notification. Must be unique within the tethering app.
|
||||
@VisibleForTesting
|
||||
static final int NO_UPSTREAM_NOTIFICATION_ID = 1002;
|
||||
@VisibleForTesting
|
||||
static final int NO_ICON_ID = 0;
|
||||
@VisibleForTesting
|
||||
@@ -71,14 +92,16 @@ public class TetheringNotificationUpdater {
|
||||
private final Context mContext;
|
||||
private final NotificationManager mNotificationManager;
|
||||
private final NotificationChannel mChannel;
|
||||
private final Handler mHandler;
|
||||
|
||||
// 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
|
||||
// relies on these values being initialized to 0 or false, 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;
|
||||
private boolean mNoUpstream = false;
|
||||
|
||||
// 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
|
||||
@@ -87,10 +110,30 @@ public class TetheringNotificationUpdater {
|
||||
// INVALID_SUBSCRIPTION_ID.
|
||||
private volatile int mActiveDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
|
||||
|
||||
@IntDef({ENABLE_NOTIFICATION_ID, RESTRICTED_NOTIFICATION_ID})
|
||||
@IntDef({ENABLE_NOTIFICATION_ID, RESTRICTED_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID})
|
||||
@interface NotificationId {}
|
||||
|
||||
public TetheringNotificationUpdater(@NonNull final Context context) {
|
||||
private static final class MccMncOverrideInfo {
|
||||
public final List<String> visitedMccMncs;
|
||||
public final int homeMcc;
|
||||
public final int homeMnc;
|
||||
MccMncOverrideInfo(List<String> visitedMccMncs, int mcc, int mnc) {
|
||||
this.visitedMccMncs = visitedMccMncs;
|
||||
this.homeMcc = mcc;
|
||||
this.homeMnc = mnc;
|
||||
}
|
||||
}
|
||||
|
||||
private static final SparseArray<MccMncOverrideInfo> sCarrierIdToMccMnc = new SparseArray<>();
|
||||
|
||||
static {
|
||||
// VZW
|
||||
sCarrierIdToMccMnc.put(
|
||||
1839, new MccMncOverrideInfo(Arrays.asList(new String[] {"20404"}), 311, 480));
|
||||
}
|
||||
|
||||
public TetheringNotificationUpdater(@NonNull final Context context,
|
||||
@NonNull final Looper looper) {
|
||||
mContext = context;
|
||||
mNotificationManager = (NotificationManager) context.createContextAsUser(UserHandle.ALL, 0)
|
||||
.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
@@ -99,6 +142,22 @@ public class TetheringNotificationUpdater {
|
||||
context.getResources().getString(R.string.notification_channel_tethering_status),
|
||||
NotificationManager.IMPORTANCE_LOW);
|
||||
mNotificationManager.createNotificationChannel(mChannel);
|
||||
mHandler = new NotificationHandler(looper);
|
||||
}
|
||||
|
||||
private class NotificationHandler extends Handler {
|
||||
NotificationHandler(Looper looper) {
|
||||
super(looper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch(msg.what) {
|
||||
case EVENT_SHOW_NO_UPSTREAM:
|
||||
notifyTetheringNoUpstream();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Called when downstream has changed */
|
||||
@@ -106,6 +165,7 @@ public class TetheringNotificationUpdater {
|
||||
if (mDownstreamTypesMask == downstreamTypesMask) return;
|
||||
mDownstreamTypesMask = downstreamTypesMask;
|
||||
updateEnableNotification();
|
||||
updateNoUpstreamNotification();
|
||||
}
|
||||
|
||||
/** Called when active data subscription id changed */
|
||||
@@ -113,21 +173,62 @@ public class TetheringNotificationUpdater {
|
||||
if (mActiveDataSubId == subId) return;
|
||||
mActiveDataSubId = subId;
|
||||
updateEnableNotification();
|
||||
updateNoUpstreamNotification();
|
||||
}
|
||||
|
||||
/** Called when upstream network changed */
|
||||
public void onUpstreamNetworkChanged(@Nullable final Network network) {
|
||||
final boolean isNoUpstream = (network == null);
|
||||
if (mNoUpstream == isNoUpstream) return;
|
||||
mNoUpstream = isNoUpstream;
|
||||
updateNoUpstreamNotification();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@VisibleForTesting
|
||||
Resources getResourcesForSubId(@NonNull final Context c, final int subId) {
|
||||
return SubscriptionManager.getResourcesForSubId(c, subId);
|
||||
final Handler getHandler() {
|
||||
return mHandler;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@VisibleForTesting
|
||||
Resources getResourcesForSubId(@NonNull final Context context, final int subId) {
|
||||
final Resources res = SubscriptionManager.getResourcesForSubId(context, subId);
|
||||
final TelephonyManager tm =
|
||||
((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE))
|
||||
.createForSubscriptionId(mActiveDataSubId);
|
||||
final int carrierId = tm.getSimCarrierId();
|
||||
final String mccmnc = tm.getSimOperator();
|
||||
final MccMncOverrideInfo overrideInfo = sCarrierIdToMccMnc.get(carrierId);
|
||||
if (overrideInfo != null && overrideInfo.visitedMccMncs.contains(mccmnc)) {
|
||||
// Re-configure MCC/MNC value to specific carrier to get right resources.
|
||||
final Configuration config = res.getConfiguration();
|
||||
config.mcc = overrideInfo.homeMcc;
|
||||
config.mnc = overrideInfo.homeMnc;
|
||||
return context.createConfigurationContext(config).getResources();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private void updateEnableNotification() {
|
||||
final boolean tetheringInactive = mDownstreamTypesMask <= DOWNSTREAM_NONE;
|
||||
final boolean tetheringInactive = mDownstreamTypesMask == DOWNSTREAM_NONE;
|
||||
|
||||
if (tetheringInactive || setupNotification() == NO_NOTIFY) {
|
||||
clearNotification(ENABLE_NOTIFICATION_ID);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateNoUpstreamNotification() {
|
||||
final boolean tetheringInactive = mDownstreamTypesMask == DOWNSTREAM_NONE;
|
||||
|
||||
if (tetheringInactive
|
||||
|| !mNoUpstream
|
||||
|| setupNoUpstreamNotification() == NO_NOTIFY) {
|
||||
clearNotification(NO_UPSTREAM_NOTIFICATION_ID);
|
||||
mHandler.removeMessages(EVENT_SHOW_NO_UPSTREAM);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void tetheringRestrictionLifted() {
|
||||
clearNotification(RESTRICTED_NOTIFICATION_ID);
|
||||
@@ -142,9 +243,38 @@ public class TetheringNotificationUpdater {
|
||||
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);
|
||||
if (isEmpty(title) || isEmpty(message)) return;
|
||||
|
||||
final PendingIntent pi = PendingIntent.getActivity(
|
||||
mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */),
|
||||
0 /* requestCode */,
|
||||
new Intent(Settings.ACTION_TETHER_SETTINGS),
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK,
|
||||
null /* options */);
|
||||
|
||||
showNotification(R.drawable.stat_sys_tether_general, title, message,
|
||||
RESTRICTED_NOTIFICATION_ID);
|
||||
RESTRICTED_NOTIFICATION_ID, pi, new Action[0]);
|
||||
}
|
||||
|
||||
private void notifyTetheringNoUpstream() {
|
||||
final Resources res = getResourcesForSubId(mContext, mActiveDataSubId);
|
||||
final String title = res.getString(R.string.no_upstream_notification_title);
|
||||
final String message = res.getString(R.string.no_upstream_notification_message);
|
||||
final String disableButton =
|
||||
res.getString(R.string.no_upstream_notification_disable_button);
|
||||
if (isEmpty(title) || isEmpty(message) || isEmpty(disableButton)) return;
|
||||
|
||||
final Intent intent = new Intent(ACTION_DISABLE_TETHERING);
|
||||
intent.setPackage(mContext.getPackageName());
|
||||
final PendingIntent pi = PendingIntent.getBroadcast(
|
||||
mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */),
|
||||
0 /* requestCode */,
|
||||
intent,
|
||||
0 /* flags */);
|
||||
final Action action = new Action.Builder(NO_ICON_ID, disableButton, pi).build();
|
||||
|
||||
showNotification(R.drawable.stat_sys_tether_general, title, message,
|
||||
NO_UPSTREAM_NOTIFICATION_ID, null /* pendingIntent */, action);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -179,12 +309,13 @@ public class TetheringNotificationUpdater {
|
||||
*
|
||||
* @return {@link android.util.SparseArray} with downstream types and icon id info.
|
||||
*/
|
||||
@NonNull
|
||||
@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) {
|
||||
if (TextUtils.isEmpty(config)) continue;
|
||||
if (isEmpty(config)) continue;
|
||||
|
||||
final String[] elements = config.split(";");
|
||||
if (elements.length != 2) {
|
||||
@@ -204,6 +335,18 @@ public class TetheringNotificationUpdater {
|
||||
return icons;
|
||||
}
|
||||
|
||||
private boolean setupNoUpstreamNotification() {
|
||||
final Resources res = getResourcesForSubId(mContext, mActiveDataSubId);
|
||||
final int delayToShowUpstreamNotification =
|
||||
res.getInteger(R.integer.delay_to_show_no_upstream_after_no_backhaul);
|
||||
|
||||
if (delayToShowUpstreamNotification < 0) return NO_NOTIFY;
|
||||
|
||||
mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_SHOW_NO_UPSTREAM),
|
||||
delayToShowUpstreamNotification);
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
private boolean setupNotification() {
|
||||
final Resources res = getResourcesForSubId(mContext, mActiveDataSubId);
|
||||
final SparseArray<Integer> downstreamIcons =
|
||||
@@ -214,17 +357,22 @@ public class TetheringNotificationUpdater {
|
||||
|
||||
final String title = res.getString(R.string.tethering_notification_title);
|
||||
final String message = res.getString(R.string.tethering_notification_message);
|
||||
if (isEmpty(title) || isEmpty(message)) return NO_NOTIFY;
|
||||
|
||||
showNotification(iconId, title, message, ENABLE_NOTIFICATION_ID);
|
||||
final PendingIntent pi = PendingIntent.getActivity(
|
||||
mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */),
|
||||
0 /* requestCode */,
|
||||
new Intent(Settings.ACTION_TETHER_SETTINGS),
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK,
|
||||
null /* options */);
|
||||
|
||||
showNotification(iconId, title, message, ENABLE_NOTIFICATION_ID, pi, new Action[0]);
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
private void showNotification(@DrawableRes final int iconId, @NonNull final String title,
|
||||
@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),
|
||||
0 /* requestCode */, intent, 0 /* flags */, null /* options */);
|
||||
@NonNull final String message, @NotificationId final int id, @Nullable PendingIntent pi,
|
||||
@NonNull final Action... actions) {
|
||||
final Notification notification =
|
||||
new Notification.Builder(mContext, mChannel.getId())
|
||||
.setSmallIcon(iconId)
|
||||
@@ -236,6 +384,7 @@ public class TetheringNotificationUpdater {
|
||||
.setVisibility(Notification.VISIBILITY_PUBLIC)
|
||||
.setCategory(Notification.CATEGORY_STATUS)
|
||||
.setContentIntent(pi)
|
||||
.setActions(actions)
|
||||
.build();
|
||||
|
||||
mNotificationManager.notify(null /* tag */, id, notification);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.networkstack.tethering.tests.unit">
|
||||
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
|
||||
<uses-permission android:name="android.permission.TETHER_PRIVILEGED"/>
|
||||
|
||||
<application android:debuggable="true">
|
||||
|
||||
@@ -23,14 +23,26 @@ 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.net.Network
|
||||
import android.os.Handler
|
||||
import android.os.HandlerThread
|
||||
import android.os.Looper
|
||||
import android.os.UserHandle
|
||||
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import android.telephony.TelephonyManager
|
||||
import androidx.test.filters.SmallTest
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import com.android.internal.util.test.BroadcastInterceptingContext
|
||||
import com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE
|
||||
import com.android.networkstack.tethering.TetheringNotificationUpdater.ENABLE_NOTIFICATION_ID
|
||||
import com.android.networkstack.tethering.TetheringNotificationUpdater.EVENT_SHOW_NO_UPSTREAM
|
||||
import com.android.networkstack.tethering.TetheringNotificationUpdater.NO_UPSTREAM_NOTIFICATION_ID
|
||||
import com.android.networkstack.tethering.TetheringNotificationUpdater.RESTRICTED_NOTIFICATION_ID
|
||||
import com.android.testutils.waitForIdle
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -43,8 +55,8 @@ 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.Mockito.verifyZeroInteractions
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
const val TEST_SUBID = 1
|
||||
@@ -55,10 +67,13 @@ 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 TITLE = "Tethering active"
|
||||
const val MESSAGE = "Tap here to set up."
|
||||
const val TEST_TITTLE = "Hotspot active"
|
||||
const val TEST_TITLE = "Hotspot active"
|
||||
const val TEST_MESSAGE = "Tap to set up hotspot."
|
||||
const val TEST_NO_UPSTREAM_TITLE = "Hotspot has no internet access"
|
||||
const val TEST_NO_UPSTREAM_MESSAGE = "Device cannot connect to internet."
|
||||
const val TEST_NO_UPSTREAM_BUTTON = "Turn off hotspot"
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@SmallTest
|
||||
@@ -67,12 +82,15 @@ class TetheringNotificationUpdaterTest {
|
||||
// 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 telephonyManager: TelephonyManager
|
||||
@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).
|
||||
// lateinit for these classes under test, as they should be reset to a different instance for
|
||||
// every test but should always be initialized before use (or the test should crash).
|
||||
private lateinit var context: TestContext
|
||||
private lateinit var notificationUpdater: TetheringNotificationUpdater
|
||||
private lateinit var fakeTetheringThread: HandlerThread
|
||||
|
||||
private val ENABLE_ICON_CONFIGS = arrayOf(
|
||||
"USB;android.test:drawable/usb", "BT;android.test:drawable/bluetooth",
|
||||
@@ -82,11 +100,19 @@ class TetheringNotificationUpdaterTest {
|
||||
private inner class TestContext(c: Context) : BroadcastInterceptingContext(c) {
|
||||
override fun createContextAsUser(user: UserHandle, flags: Int) =
|
||||
if (user == UserHandle.ALL) mockContext else this
|
||||
override fun getSystemService(name: String) =
|
||||
if (name == Context.TELEPHONY_SERVICE) telephonyManager
|
||||
else super.getSystemService(name)
|
||||
}
|
||||
|
||||
private inner class WrappedNotificationUpdater(c: Context) : TetheringNotificationUpdater(c) {
|
||||
private inner class WrappedNotificationUpdater(c: Context, looper: Looper)
|
||||
: TetheringNotificationUpdater(c, looper) {
|
||||
override fun getResourcesForSubId(context: Context, subId: Int) =
|
||||
if (subId == TEST_SUBID) testResources else defaultResources
|
||||
when (subId) {
|
||||
TEST_SUBID -> testResources
|
||||
INVALID_SUBSCRIPTION_ID -> defaultResources
|
||||
else -> super.getResourcesForSubId(context, subId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupResources() {
|
||||
@@ -94,12 +120,20 @@ class TetheringNotificationUpdaterTest {
|
||||
.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(5).`when`(testResources)
|
||||
.getInteger(R.integer.delay_to_show_no_upstream_after_no_backhaul)
|
||||
doReturn(TITLE).`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_TITLE).`when`(testResources).getString(R.string.tethering_notification_title)
|
||||
doReturn(TEST_MESSAGE).`when`(testResources)
|
||||
.getString(R.string.tethering_notification_message)
|
||||
doReturn(TEST_NO_UPSTREAM_TITLE).`when`(testResources)
|
||||
.getString(R.string.no_upstream_notification_title)
|
||||
doReturn(TEST_NO_UPSTREAM_MESSAGE).`when`(testResources)
|
||||
.getString(R.string.no_upstream_notification_message)
|
||||
doReturn(TEST_NO_UPSTREAM_BUTTON).`when`(testResources)
|
||||
.getString(R.string.no_upstream_notification_disable_button)
|
||||
doReturn(USB_ICON_ID).`when`(defaultResources)
|
||||
.getIdentifier(eq("android.test:drawable/usb"), any(), any())
|
||||
doReturn(BT_ICON_ID).`when`(defaultResources)
|
||||
@@ -113,35 +147,61 @@ class TetheringNotificationUpdaterTest {
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
val context = TestContext(InstrumentationRegistry.getInstrumentation().context)
|
||||
context = TestContext(InstrumentationRegistry.getInstrumentation().context)
|
||||
doReturn(notificationManager).`when`(mockContext)
|
||||
.getSystemService(Context.NOTIFICATION_SERVICE)
|
||||
notificationUpdater = WrappedNotificationUpdater(context)
|
||||
fakeTetheringThread = HandlerThread(this::class.simpleName)
|
||||
fakeTetheringThread.start()
|
||||
notificationUpdater = WrappedNotificationUpdater(context, fakeTetheringThread.looper)
|
||||
setupResources()
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
fakeTetheringThread.quitSafely()
|
||||
}
|
||||
|
||||
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())
|
||||
private fun verifyNotification(iconId: Int, title: String, text: String, id: Int) {
|
||||
verify(notificationManager, never()).cancel(any(), eq(id))
|
||||
|
||||
val notificationCaptor = ArgumentCaptor.forClass(Notification::class.java)
|
||||
verify(notificationManager, times(1))
|
||||
.notify(any(), anyInt(), notificationCaptor.capture())
|
||||
.notify(any(), eq(id), notificationCaptor.capture())
|
||||
|
||||
val notification = notificationCaptor.getValue()
|
||||
assertEquals(iconId, notification.smallIcon.resId)
|
||||
assertEquals(title, notification.title())
|
||||
assertEquals(text, notification.text())
|
||||
}
|
||||
|
||||
private fun verifyNotificationCancelled(id: Int) =
|
||||
verify(notificationManager, times(1)).cancel(any(), eq(id))
|
||||
|
||||
private val tetheringActiveNotifications =
|
||||
listOf(NO_UPSTREAM_NOTIFICATION_ID, ENABLE_NOTIFICATION_ID)
|
||||
|
||||
private fun verifyCancelAllTetheringActiveNotifications() {
|
||||
tetheringActiveNotifications.forEach {
|
||||
verifyNotificationCancelled(it)
|
||||
}
|
||||
reset(notificationManager)
|
||||
}
|
||||
|
||||
private fun verifyNoNotification() {
|
||||
verify(notificationManager, times(1)).cancel(any(), anyInt())
|
||||
verify(notificationManager, never()).notify(any(), anyInt(), any())
|
||||
|
||||
private fun verifyOnlyTetheringActiveNotification(
|
||||
notifyId: Int,
|
||||
iconId: Int,
|
||||
title: String,
|
||||
text: String
|
||||
) {
|
||||
tetheringActiveNotifications.forEach {
|
||||
when (it) {
|
||||
notifyId -> verifyNotification(iconId, title, text, notifyId)
|
||||
else -> verifyNotificationCancelled(it)
|
||||
}
|
||||
}
|
||||
reset(notificationManager)
|
||||
}
|
||||
|
||||
@@ -149,7 +209,7 @@ class TetheringNotificationUpdaterTest {
|
||||
fun testNotificationWithDownstreamChanged() {
|
||||
// Wifi downstream. No notification.
|
||||
notificationUpdater.onDownstreamChanged(WIFI_MASK)
|
||||
verifyNoNotification()
|
||||
verifyCancelAllTetheringActiveNotifications()
|
||||
|
||||
// Same downstream changed. Nothing happened.
|
||||
notificationUpdater.onDownstreamChanged(WIFI_MASK)
|
||||
@@ -157,22 +217,23 @@ class TetheringNotificationUpdaterTest {
|
||||
|
||||
// Wifi and usb downstreams. Show enable notification
|
||||
notificationUpdater.onDownstreamChanged(WIFI_MASK or USB_MASK)
|
||||
verifyNotification(GENERAL_ICON_ID, TITTLE, MESSAGE)
|
||||
verifyOnlyTetheringActiveNotification(
|
||||
ENABLE_NOTIFICATION_ID, GENERAL_ICON_ID, TITLE, MESSAGE)
|
||||
|
||||
// Usb downstream. Still show enable notification.
|
||||
notificationUpdater.onDownstreamChanged(USB_MASK)
|
||||
verifyNotification(USB_ICON_ID, TITTLE, MESSAGE)
|
||||
verifyOnlyTetheringActiveNotification(ENABLE_NOTIFICATION_ID, USB_ICON_ID, TITLE, MESSAGE)
|
||||
|
||||
// No downstream. No notification.
|
||||
notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE)
|
||||
verifyNoNotification()
|
||||
verifyCancelAllTetheringActiveNotifications()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationWithActiveDataSubscriptionIdChanged() {
|
||||
// Usb downstream. Showed enable notification with default resource.
|
||||
notificationUpdater.onDownstreamChanged(USB_MASK)
|
||||
verifyNotification(USB_ICON_ID, TITTLE, MESSAGE)
|
||||
verifyOnlyTetheringActiveNotification(ENABLE_NOTIFICATION_ID, USB_ICON_ID, TITLE, MESSAGE)
|
||||
|
||||
// Same subId changed. Nothing happened.
|
||||
notificationUpdater.onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID)
|
||||
@@ -180,15 +241,16 @@ class TetheringNotificationUpdaterTest {
|
||||
|
||||
// Set test sub id. Clear notification with test resource.
|
||||
notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID)
|
||||
verifyNoNotification()
|
||||
verifyCancelAllTetheringActiveNotifications()
|
||||
|
||||
// Wifi downstream. Show enable notification with test resource.
|
||||
notificationUpdater.onDownstreamChanged(WIFI_MASK)
|
||||
verifyNotification(WIFI_ICON_ID, TEST_TITTLE, TEST_MESSAGE)
|
||||
verifyOnlyTetheringActiveNotification(
|
||||
ENABLE_NOTIFICATION_ID, WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE)
|
||||
|
||||
// No downstream. No notification.
|
||||
notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE)
|
||||
verifyNoNotification()
|
||||
verifyCancelAllTetheringActiveNotifications()
|
||||
}
|
||||
|
||||
private fun assertIconNumbers(number: Int, configs: Array<String?>) {
|
||||
@@ -227,10 +289,8 @@ class TetheringNotificationUpdaterTest {
|
||||
|
||||
@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 title = context.resources.getString(R.string.disable_tether_notification_title)
|
||||
val message = 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)
|
||||
@@ -244,18 +304,127 @@ class TetheringNotificationUpdaterTest {
|
||||
|
||||
// User restrictions on. Show restricted notification.
|
||||
notificationUpdater.notifyTetheringDisabledByRestriction()
|
||||
verifyNotification(R.drawable.stat_sys_tether_general, title, message)
|
||||
verifyNotification(R.drawable.stat_sys_tether_general, title, message,
|
||||
RESTRICTED_NOTIFICATION_ID)
|
||||
reset(notificationManager)
|
||||
|
||||
// User restrictions off. Clear notification.
|
||||
notificationUpdater.tetheringRestrictionLifted()
|
||||
verifyNoNotification()
|
||||
verifyNotificationCancelled(RESTRICTED_NOTIFICATION_ID)
|
||||
reset(notificationManager)
|
||||
|
||||
// Set test sub id. No notification.
|
||||
notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID)
|
||||
verifyNoNotification()
|
||||
verifyCancelAllTetheringActiveNotifications()
|
||||
|
||||
// User restrictions on again. Show restricted notification with test resource.
|
||||
notificationUpdater.notifyTetheringDisabledByRestriction()
|
||||
verifyNotification(R.drawable.stat_sys_tether_general, disallowTitle, disallowMessage)
|
||||
verifyNotification(R.drawable.stat_sys_tether_general, disallowTitle, disallowMessage,
|
||||
RESTRICTED_NOTIFICATION_ID)
|
||||
reset(notificationManager)
|
||||
}
|
||||
|
||||
val MAX_BACKOFF_MS = 200L
|
||||
/**
|
||||
* Waits for all messages, including delayed ones, to be processed.
|
||||
*
|
||||
* This will wait until the handler has no more messages to be processed including
|
||||
* delayed ones, or the timeout has expired. It uses an exponential backoff strategy
|
||||
* to wait longer and longer to consume less CPU, with the max granularity being
|
||||
* MAX_BACKOFF_MS.
|
||||
*
|
||||
* @return true if all messages have been processed including delayed ones, false if timeout
|
||||
*
|
||||
* TODO: Move this method to com.android.testutils.HandlerUtils.kt.
|
||||
*/
|
||||
private fun Handler.waitForDelayedMessage(what: Int?, timeoutMs: Long) {
|
||||
fun hasMatchingMessages() =
|
||||
if (what == null) hasMessagesOrCallbacks() else hasMessages(what)
|
||||
val expiry = System.currentTimeMillis() + timeoutMs
|
||||
var delay = 5L
|
||||
while (System.currentTimeMillis() < expiry && hasMatchingMessages()) {
|
||||
// None of Handler, Looper, Message and MessageQueue expose any way to retrieve
|
||||
// the time when the next (let alone the last) message will be processed, so
|
||||
// short of examining the internals with reflection sleep() is the only solution.
|
||||
Thread.sleep(delay)
|
||||
delay = (delay * 2)
|
||||
.coerceAtMost(expiry - System.currentTimeMillis())
|
||||
.coerceAtMost(MAX_BACKOFF_MS)
|
||||
}
|
||||
|
||||
val timeout = expiry - System.currentTimeMillis()
|
||||
if (timeout <= 0) fail("Delayed message did not process yet after ${timeoutMs}ms")
|
||||
waitForIdle(timeout)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationWithUpstreamNetworkChanged() {
|
||||
// Set test sub id. No notification.
|
||||
notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID)
|
||||
verifyCancelAllTetheringActiveNotifications()
|
||||
|
||||
// Wifi downstream. Show enable notification with test resource.
|
||||
notificationUpdater.onDownstreamChanged(WIFI_MASK)
|
||||
verifyOnlyTetheringActiveNotification(
|
||||
ENABLE_NOTIFICATION_ID, WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE)
|
||||
|
||||
// There is no upstream. Show no upstream notification.
|
||||
notificationUpdater.onUpstreamNetworkChanged(null)
|
||||
notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, 500L)
|
||||
verifyNotification(R.drawable.stat_sys_tether_general, TEST_NO_UPSTREAM_TITLE,
|
||||
TEST_NO_UPSTREAM_MESSAGE, NO_UPSTREAM_NOTIFICATION_ID)
|
||||
reset(notificationManager)
|
||||
|
||||
// Same upstream network changed. Nothing happened.
|
||||
notificationUpdater.onUpstreamNetworkChanged(null)
|
||||
verifyZeroInteractions(notificationManager)
|
||||
|
||||
// Upstream come back. Clear no upstream notification.
|
||||
notificationUpdater.onUpstreamNetworkChanged(Network(1000))
|
||||
verifyNotificationCancelled(NO_UPSTREAM_NOTIFICATION_ID)
|
||||
reset(notificationManager)
|
||||
|
||||
// No upstream again. Show no upstream notification.
|
||||
notificationUpdater.onUpstreamNetworkChanged(null)
|
||||
notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, 500L)
|
||||
verifyNotification(R.drawable.stat_sys_tether_general, TEST_NO_UPSTREAM_TITLE,
|
||||
TEST_NO_UPSTREAM_MESSAGE, NO_UPSTREAM_NOTIFICATION_ID)
|
||||
reset(notificationManager)
|
||||
|
||||
// No downstream. No notification.
|
||||
notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE)
|
||||
verifyCancelAllTetheringActiveNotifications()
|
||||
|
||||
// Set R.integer.delay_to_show_no_upstream_after_no_backhaul to 0 and have wifi downstream
|
||||
// again. Show enable notification only.
|
||||
doReturn(-1).`when`(testResources)
|
||||
.getInteger(R.integer.delay_to_show_no_upstream_after_no_backhaul)
|
||||
notificationUpdater.onDownstreamChanged(WIFI_MASK)
|
||||
notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, 500L)
|
||||
verifyOnlyTetheringActiveNotification(
|
||||
ENABLE_NOTIFICATION_ID, WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetResourcesForSubId() {
|
||||
doReturn(telephonyManager).`when`(telephonyManager).createForSubscriptionId(anyInt())
|
||||
doReturn(1234).`when`(telephonyManager).getSimCarrierId()
|
||||
doReturn("000000").`when`(telephonyManager).getSimOperator()
|
||||
|
||||
val subId = -2 // Use invalid subId to avoid getting resource from cache or real subId.
|
||||
val config = context.resources.configuration
|
||||
var res = notificationUpdater.getResourcesForSubId(context, subId)
|
||||
assertEquals(config.mcc, res.configuration.mcc)
|
||||
assertEquals(config.mnc, res.configuration.mnc)
|
||||
|
||||
doReturn(1839).`when`(telephonyManager).getSimCarrierId()
|
||||
res = notificationUpdater.getResourcesForSubId(context, subId)
|
||||
assertEquals(config.mcc, res.configuration.mcc)
|
||||
assertEquals(config.mnc, res.configuration.mnc)
|
||||
|
||||
doReturn("20404").`when`(telephonyManager).getSimOperator()
|
||||
res = notificationUpdater.getResourcesForSubId(context, subId)
|
||||
assertEquals(311, res.configuration.mcc)
|
||||
assertEquals(480, res.configuration.mnc)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,7 +383,7 @@ public class TetheringTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TetheringNotificationUpdater getNotificationUpdater(Context ctx) {
|
||||
public TetheringNotificationUpdater getNotificationUpdater(Context ctx, Looper looper) {
|
||||
return mNotificationUpdater;
|
||||
}
|
||||
}
|
||||
@@ -1691,6 +1691,18 @@ public class TetheringTest {
|
||||
assertEquals(clientAddrParceled, params.clientAddr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpstreamNetworkChanged() {
|
||||
final Tethering.TetherMasterSM stateMachine = (Tethering.TetherMasterSM)
|
||||
mTetheringDependencies.mUpstreamNetworkMonitorMasterSM;
|
||||
final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
|
||||
when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any())).thenReturn(upstreamState);
|
||||
stateMachine.chooseUpstreamType(true);
|
||||
|
||||
verify(mUpstreamNetworkMonitor, times(1)).setCurrentUpstream(eq(upstreamState.network));
|
||||
verify(mNotificationUpdater, times(1)).onUpstreamNetworkChanged(eq(upstreamState.network));
|
||||
}
|
||||
|
||||
// TODO: Test that a request for hotspot mode doesn't interfere with an
|
||||
// already operating tethering mode interface.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user