[TNU05] Add no upstream notification

Reminder user of unavailable tethering status if there is no
internet access.

Bug: 147818698
Test: atest TetheringTests
Change-Id: Ic6557f9f7703337596100cd6a477fd7239217166
Merged-In: Ic6557f9f7703337596100cd6a477fd7239217166
(cherry picked from commit ac1b098acc504b60e85b3dcd22941f4e293865ae, aosp/1237036)
This commit is contained in:
Paul Hu
2020-04-16 02:54:37 +00:00
parent 8c98b3d35e
commit af6a2a33f5
11 changed files with 446 additions and 87 deletions

View File

@@ -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) {

View File

@@ -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);
}
/**

View File

@@ -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);