summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt18
-rw-r--r--packages/Tethering/AndroidManifest.xml3
-rw-r--r--packages/Tethering/res/values-mcc204-mnc04/strings.xml30
-rw-r--r--packages/Tethering/res/values-mcc310-mnc004/config.xml20
-rw-r--r--packages/Tethering/res/values-mcc311-mnc480/config.xml20
-rw-r--r--packages/Tethering/res/values/config.xml6
-rw-r--r--packages/Tethering/src/com/android/networkstack/tethering/Tethering.java10
-rw-r--r--packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java5
-rw-r--r--packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java185
-rw-r--r--packages/Tethering/tests/unit/AndroidManifest.xml1
-rw-r--r--packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt239
-rw-r--r--packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java14
-rw-r--r--telephony/java/android/telephony/AccessNetworkConstants.java41
-rw-r--r--telephony/java/android/telephony/AccessNetworkUtils.java46
14 files changed, 541 insertions, 97 deletions
diff --git a/api/current.txt b/api/current.txt
index 317ec9341e99..2d86ace263c2 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -44557,14 +44557,26 @@ package android.telephony {
field public static final int BAND_46 = 46; // 0x2e
field public static final int BAND_47 = 47; // 0x2f
field public static final int BAND_48 = 48; // 0x30
+ field public static final int BAND_49 = 49; // 0x31
field public static final int BAND_5 = 5; // 0x5
+ field public static final int BAND_50 = 50; // 0x32
+ field public static final int BAND_51 = 51; // 0x33
+ field public static final int BAND_52 = 52; // 0x34
+ field public static final int BAND_53 = 53; // 0x35
field public static final int BAND_6 = 6; // 0x6
field public static final int BAND_65 = 65; // 0x41
field public static final int BAND_66 = 66; // 0x42
field public static final int BAND_68 = 68; // 0x44
field public static final int BAND_7 = 7; // 0x7
field public static final int BAND_70 = 70; // 0x46
+ field public static final int BAND_71 = 71; // 0x47
+ field public static final int BAND_72 = 72; // 0x48
+ field public static final int BAND_73 = 73; // 0x49
+ field public static final int BAND_74 = 74; // 0x4a
field public static final int BAND_8 = 8; // 0x8
+ field public static final int BAND_85 = 85; // 0x55
+ field public static final int BAND_87 = 87; // 0x57
+ field public static final int BAND_88 = 88; // 0x58
field public static final int BAND_9 = 9; // 0x9
}
@@ -44628,7 +44640,13 @@ package android.telephony {
field public static final int BAND_83 = 83; // 0x53
field public static final int BAND_84 = 84; // 0x54
field public static final int BAND_86 = 86; // 0x56
+ field public static final int BAND_89 = 89; // 0x59
field public static final int BAND_90 = 90; // 0x5a
+ field public static final int BAND_91 = 91; // 0x5b
+ field public static final int BAND_92 = 92; // 0x5c
+ field public static final int BAND_93 = 93; // 0x5d
+ field public static final int BAND_94 = 94; // 0x5e
+ field public static final int BAND_95 = 95; // 0x5f
}
public static final class AccessNetworkConstants.UtranBand {
diff --git a/packages/Tethering/AndroidManifest.xml b/packages/Tethering/AndroidManifest.xml
index 1dc8227e81f4..2b2fe4534c3e 100644
--- a/packages/Tethering/AndroidManifest.xml
+++ b/packages/Tethering/AndroidManifest.xml
@@ -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"
diff --git a/packages/Tethering/res/values-mcc204-mnc04/strings.xml b/packages/Tethering/res/values-mcc204-mnc04/strings.xml
deleted file mode 100644
index 9dadd49cf8a4..000000000000
--- a/packages/Tethering/res/values-mcc204-mnc04/strings.xml
+++ /dev/null
@@ -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>
diff --git a/packages/Tethering/res/values-mcc310-mnc004/config.xml b/packages/Tethering/res/values-mcc310-mnc004/config.xml
new file mode 100644
index 000000000000..8c627d5df058
--- /dev/null
+++ b/packages/Tethering/res/values-mcc310-mnc004/config.xml
@@ -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> \ No newline at end of file
diff --git a/packages/Tethering/res/values-mcc311-mnc480/config.xml b/packages/Tethering/res/values-mcc311-mnc480/config.xml
new file mode 100644
index 000000000000..8c627d5df058
--- /dev/null
+++ b/packages/Tethering/res/values-mcc311-mnc480/config.xml
@@ -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> \ No newline at end of file
diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml
index 3a2084ac927b..780a015961b4 100644
--- a/packages/Tethering/res/values/config.xml
+++ b/packages/Tethering/res/values/config.xml
@@ -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>
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 367ce9bd5c38..14d288699e99 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -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();
}
}
@@ -2015,6 +2022,7 @@ public class Tethering {
} finally {
mTetheringEventCallbacks.finishBroadcast();
}
+ mNotificationUpdater.onUpstreamNetworkChanged(network);
}
private void reportConfigurationChanged(TetheringConfigurationParcel config) {
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index 893c5823dce1..9b54b5ff2403 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -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);
}
/**
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java
index 42870560cb5e..ff83fd1e4f1e 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java
@@ -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, 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,
- RESTRICTED_NOTIFICATION_ID);
+ 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);
diff --git a/packages/Tethering/tests/unit/AndroidManifest.xml b/packages/Tethering/tests/unit/AndroidManifest.xml
index 55640db69324..31eaabff5274 100644
--- a/packages/Tethering/tests/unit/AndroidManifest.xml
+++ b/packages/Tethering/tests/unit/AndroidManifest.xml
@@ -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">
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
index 7bff74b25d94..5f8858857c75 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
@@ -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)
}
}
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 5bec51332230..15e253af12a9 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -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.
}
diff --git a/telephony/java/android/telephony/AccessNetworkConstants.java b/telephony/java/android/telephony/AccessNetworkConstants.java
index 558f4cd24471..39a754389254 100644
--- a/telephony/java/android/telephony/AccessNetworkConstants.java
+++ b/telephony/java/android/telephony/AccessNetworkConstants.java
@@ -19,9 +19,9 @@ package android.telephony;
import android.annotation.IntDef;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.hardware.radio.V1_1.EutranBands;
import android.hardware.radio.V1_1.GeranBands;
import android.hardware.radio.V1_5.AccessNetwork;
+import android.hardware.radio.V1_5.EutranBands;
import android.hardware.radio.V1_5.UtranBands;
import java.lang.annotation.Retention;
@@ -212,7 +212,8 @@ public final class AccessNetworkConstants {
/**
* Frequency bands for EUTRAN.
- * http://www.etsi.org/deliver/etsi_ts/136100_136199/136101/14.03.00_60/ts_136101v140p.pdf
+ * 3GPP TS 36.101, Version 16.4.0, Table 5.5: Operating bands
+ * https://www.etsi.org/deliver/etsi_ts/136100_136199/136101/15.09.00_60/ts_136101v150900p.pdf
*/
public static final class EutranBand {
public static final int BAND_1 = EutranBands.BAND_1;
@@ -259,10 +260,22 @@ public final class AccessNetworkConstants {
public static final int BAND_46 = EutranBands.BAND_46;
public static final int BAND_47 = EutranBands.BAND_47;
public static final int BAND_48 = EutranBands.BAND_48;
+ public static final int BAND_49 = EutranBands.BAND_49;
+ public static final int BAND_50 = EutranBands.BAND_50;
+ public static final int BAND_51 = EutranBands.BAND_51;
+ public static final int BAND_52 = EutranBands.BAND_52;
+ public static final int BAND_53 = EutranBands.BAND_53;
public static final int BAND_65 = EutranBands.BAND_65;
public static final int BAND_66 = EutranBands.BAND_66;
public static final int BAND_68 = EutranBands.BAND_68;
public static final int BAND_70 = EutranBands.BAND_70;
+ public static final int BAND_71 = EutranBands.BAND_71;
+ public static final int BAND_72 = EutranBands.BAND_72;
+ public static final int BAND_73 = EutranBands.BAND_73;
+ public static final int BAND_74 = EutranBands.BAND_74;
+ public static final int BAND_85 = EutranBands.BAND_85;
+ public static final int BAND_87 = EutranBands.BAND_87;
+ public static final int BAND_88 = EutranBands.BAND_88;
/** @hide */
private EutranBand() {};
@@ -305,9 +318,11 @@ public final class AccessNetworkConstants {
/**
* Frequency bands for NGRAN
+ * https://www.etsi.org/deliver/etsi_ts/138100_138199/13810101/15.08.02_60/ts_13810101v150802p.pdf
+ * https://www.etsi.org/deliver/etsi_ts/138100_138199/13810102/15.08.00_60/ts_13810102v150800p.pdf
*/
public static final class NgranBands {
- /** FR1 bands */
+ /** 3GPP TS 38.101-1, Version 16.2.0, Table 5.2-1: FR1 bands */
public static final int BAND_1 = android.hardware.radio.V1_5.NgranBands.BAND_1;
public static final int BAND_2 = android.hardware.radio.V1_5.NgranBands.BAND_2;
public static final int BAND_3 = android.hardware.radio.V1_5.NgranBands.BAND_3;
@@ -346,9 +361,15 @@ public final class AccessNetworkConstants {
public static final int BAND_83 = android.hardware.radio.V1_5.NgranBands.BAND_83;
public static final int BAND_84 = android.hardware.radio.V1_5.NgranBands.BAND_84;
public static final int BAND_86 = android.hardware.radio.V1_5.NgranBands.BAND_86;
+ public static final int BAND_89 = android.hardware.radio.V1_5.NgranBands.BAND_89;
public static final int BAND_90 = android.hardware.radio.V1_5.NgranBands.BAND_90;
+ public static final int BAND_91 = android.hardware.radio.V1_5.NgranBands.BAND_91;
+ public static final int BAND_92 = android.hardware.radio.V1_5.NgranBands.BAND_92;
+ public static final int BAND_93 = android.hardware.radio.V1_5.NgranBands.BAND_93;
+ public static final int BAND_94 = android.hardware.radio.V1_5.NgranBands.BAND_94;
+ public static final int BAND_95 = android.hardware.radio.V1_5.NgranBands.BAND_95;
- /** FR2 bands */
+ /** 3GPP TS 38.101-2, Version 16.2.0, Table 5.2-1: FR2 bands */
public static final int BAND_257 = android.hardware.radio.V1_5.NgranBands.BAND_257;
public static final int BAND_258 = android.hardware.radio.V1_5.NgranBands.BAND_258;
public static final int BAND_260 = android.hardware.radio.V1_5.NgranBands.BAND_260;
@@ -398,7 +419,13 @@ public final class AccessNetworkConstants {
BAND_83,
BAND_84,
BAND_86,
+ BAND_89,
BAND_90,
+ BAND_91,
+ BAND_92,
+ BAND_93,
+ BAND_94,
+ BAND_95,
BAND_257,
BAND_258,
BAND_260,
@@ -495,7 +522,13 @@ public final class AccessNetworkConstants {
case BAND_83:
case BAND_84:
case BAND_86:
+ case BAND_89:
case BAND_90:
+ case BAND_91:
+ case BAND_92:
+ case BAND_93:
+ case BAND_94:
+ case BAND_95:
return FREQUENCY_RANGE_GROUP_1;
case BAND_257:
case BAND_258:
diff --git a/telephony/java/android/telephony/AccessNetworkUtils.java b/telephony/java/android/telephony/AccessNetworkUtils.java
index 5d2c225f28ec..981ed450004a 100644
--- a/telephony/java/android/telephony/AccessNetworkUtils.java
+++ b/telephony/java/android/telephony/AccessNetworkUtils.java
@@ -34,12 +34,10 @@ public class AccessNetworkUtils {
return DUPLEX_MODE_UNKNOWN;
}
- if (band >= EutranBand.BAND_68) {
+ if (band > EutranBand.BAND_88) {
return DUPLEX_MODE_UNKNOWN;
} else if (band >= EutranBand.BAND_65) {
return DUPLEX_MODE_FDD;
- } else if (band >= EutranBand.BAND_47) {
- return DUPLEX_MODE_UNKNOWN;
} else if (band >= EutranBand.BAND_33) {
return DUPLEX_MODE_TDD;
} else if (band >= EutranBand.BAND_1) {
@@ -58,17 +56,53 @@ public class AccessNetworkUtils {
* @return Operating band number, or {@link #INVALID_BAND} if no corresponding band exists
*/
public static int getOperatingBandForEarfcn(int earfcn) {
- if (earfcn > 67535) {
+ if (earfcn > 70645) {
+ return INVALID_BAND;
+ } else if (earfcn >= 70596) {
+ return EutranBand.BAND_88;
+ } else if (earfcn >= 70546) {
+ return EutranBand.BAND_87;
+ } else if (earfcn >= 70366) {
+ return EutranBand.BAND_85;
+ } else if (earfcn > 69465) {
+ return INVALID_BAND;
+ } else if (earfcn >= 69036) {
+ return EutranBand.BAND_74;
+ } else if (earfcn >= 68986) {
+ return EutranBand.BAND_73;
+ } else if (earfcn >= 68936) {
+ return EutranBand.BAND_72;
+ } else if (earfcn >= 68586) {
+ return EutranBand.BAND_71;
+ } else if (earfcn >= 68336) {
+ return EutranBand.BAND_70;
+ } else if (earfcn > 67835) {
return INVALID_BAND;
+ } else if (earfcn >= 67536) {
+ return EutranBand.BAND_68;
} else if (earfcn >= 67366) {
return INVALID_BAND; // band 67 only for CarrierAgg
} else if (earfcn >= 66436) {
return EutranBand.BAND_66;
} else if (earfcn >= 65536) {
return EutranBand.BAND_65;
- } else if (earfcn > 54339) {
+ } else if (earfcn > 60254) {
return INVALID_BAND;
- } else if (earfcn >= 46790 /* inferred from the end range of BAND_45 */) {
+ } else if (earfcn >= 60140) {
+ return EutranBand.BAND_53;
+ } else if (earfcn >= 59140) {
+ return EutranBand.BAND_52;
+ } else if (earfcn >= 59090) {
+ return EutranBand.BAND_51;
+ } else if (earfcn >= 58240) {
+ return EutranBand.BAND_50;
+ } else if (earfcn >= 56740) {
+ return EutranBand.BAND_49;
+ } else if (earfcn >= 55240) {
+ return EutranBand.BAND_48;
+ } else if (earfcn >= 54540) {
+ return EutranBand.BAND_47;
+ } else if (earfcn >= 46790) {
return EutranBand.BAND_46;
} else if (earfcn >= 46590) {
return EutranBand.BAND_45;