[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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user