Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bff90401 authored by paulhu's avatar paulhu
Browse files

[TNU01] Add Tethering notification updater

There are lots of Carrier/OEM requests for tethering
notification customization. So add a new tethering notification
updater class which can let OEM customize the behavior they
wanted.

Bug: 122085773
Test: atest TetheringTests
Change-Id: I7faacde7ac84e93ea0dfe03dd33d2cc41c589225
parent efbcd675
Loading
Loading
Loading
Loading
+45 −0
Original line number Diff line number Diff line
@@ -155,4 +155,49 @@

    <!-- ComponentName of the service used to run no ui tether provisioning. -->
    <string translatable="false" name="config_wifi_tether_enable">com.android.settings/.wifi.tether.TetherService</string>

    <!-- Enable tethering notification -->
    <!-- Icons for showing tether enable notification.
         Each item should have two elements and be separated with ";".

         The first element is downstream types which is one of tethering. This element has to be
         made by WIFI, USB, BT, and OR'd with the others. Use "|" to combine multiple downstream
         types and use "," to separate each combinations. Such as

             USB|BT,WIFI|USB|BT

         The second element is icon for the item. This element has to be composed by
         <package name>:drawable/<resource name>. Such as

             1. com.android.networkstack.tethering:drawable/stat_sys_tether_general
             2. android:drawable/xxx

         So the entire string of each item would be

             USB|BT,WIFI|USB|BT;com.android.networkstack.tethering:drawable/stat_sys_tether_general

         NOTE: One config can be separated into two or more for readability. Such as

               WIFI|USB,WIFI|BT,USB|BT,WIFI|USB|BT;android:drawable/xxx

               can be separated into

               WIFI|USB;android:drawable/xxx
               WIFI|BT;android:drawable/xxx
               USB|BT;android:drawable/xxx
               WIFI|USB|BT;android:drawable/xxx

         Notification will not show if the downstream type isn't listed in array.
         Empty array means disable notifications. -->
    <!-- In AOSP, hotspot is configured to no notification by default. Because status bar has showed
         an icon on the right side already -->
    <string-array translatable="false" name="tethering_notification_icons">
        <item>USB;com.android.networkstack.tethering:drawable/stat_sys_tether_usb</item>
        <item>BT;com.android.networkstack.tethering:drawable/stat_sys_tether_bluetooth</item>
        <item>WIFI|USB,WIFI|BT,USB|BT,WIFI|USB|BT;com.android.networkstack.tethering:drawable/stat_sys_tether_general</item>
    </string-array>
    <!-- String for tether enable notification title. -->
    <string name="tethering_notification_title">@string/tethered_notification_title</string>
    <!-- String for tether enable notification message. -->
    <string name="tethering_notification_message">@string/tethered_notification_message</string>
</resources>
+40 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android">
    <overlayable name="TetheringConfig">
        <policy type="product|system|vendor">
            <!-- Params from config.xml that can be overlaid -->
            <item type="array" name="config_tether_usb_regexs"/>
            <item type="array" name="config_tether_ncm_regexs" />
            <item type="array" name="config_tether_wifi_regexs"/>
@@ -31,6 +32,45 @@
            <item type="string" name="config_mobile_hotspot_provision_response"/>
            <item type="integer" name="config_mobile_hotspot_provision_check_period"/>
            <item type="string" name="config_wifi_tether_enable"/>
            <!-- Configuration values for TetheringNotificationUpdater -->
            <!-- Icons for showing tether enable notification.
            Each item should have two elements and be separated with ";".

            The first element is downstream types which is one of tethering. This element has to be
            made by WIFI, USB, BT, and OR'd with the others. Use "|" to combine multiple downstream
            types and use "," to separate each combinations. Such as

            USB|BT,WIFI|USB|BT

            The second element is icon for the item. This element has to be composed by
            <package name>:drawable/<resource name>. Such as

            1. com.android.networkstack.tethering:drawable/stat_sys_tether_general
            2. android:drawable/xxx

            So the entire string of each item would be

            USB|BT,WIFI|USB|BT;com.android.networkstack.tethering:drawable/stat_sys_tether_general

            NOTE: One config can be separated into two or more for readability. Such as

                  WIFI|USB,WIFI|BT,USB|BT,WIFI|USB|BT;android:drawable/xxx

                  can be separated into

                  WIFI|USB;android:drawable/xxx
                  WIFI|BT;android:drawable/xxx
                  USB|BT;android:drawable/xxx
                  WIFI|USB|BT;android:drawable/xxx

            Notification will not show if the downstream type isn't listed in array.
            Empty array means disable notifications. -->
            <item type="array" name="tethering_notification_icons"/>
            <!-- String for tether enable notification title. -->
            <item type="string" name="tethering_notification_title"/>
            <!-- String for tether enable notification message. -->
            <item type="string" name="tethering_notification_message"/>
            <!-- Params from config.xml that can be overlaid -->
        </policy>
    </overlayable>
</resources>
+7 −5
Original line number Diff line number Diff line
@@ -15,19 +15,21 @@
-->
<resources>
    <!-- Shown when the device is tethered -->
    <!-- Strings for tethered notification title [CHAR LIMIT=200] -->
    <!-- String for tethered notification title [CHAR LIMIT=200] -->
    <string name="tethered_notification_title">Tethering or hotspot active</string>
    <!-- Strings for tethered notification message [CHAR LIMIT=200] -->
    <!-- String for tethered notification message [CHAR LIMIT=200] -->
    <string name="tethered_notification_message">Tap to set up.</string>

    <!-- This notification is shown when tethering has been disabled on a user's device.
    The device is managed by the user's employer. Tethering can't be turned on unless the
    IT administrator allows it. The noun "admin" is another reference for "IT administrator." -->
    <!-- Strings for tether disabling notification title [CHAR LIMIT=200] -->
    <!-- String for tether disabling notification title [CHAR LIMIT=200] -->
    <string name="disable_tether_notification_title">Tethering is disabled</string>
    <!-- Strings for tether disabling notification message [CHAR LIMIT=200] -->
    <!-- String for tether disabling notification message [CHAR LIMIT=200] -->
    <string name="disable_tether_notification_message">Contact your admin for details</string>

    <!-- Strings for tether notification channel name [CHAR LIMIT=200] -->
    <!-- This string should be consistent with the "Hotspot & tethering" text in the "Network and
    Internet" settings page. That is currently the tether_settings_title_all string. -->
    <!-- String for tether notification channel name [CHAR LIMIT=200] -->
    <string name="notification_channel_tethering_status">Hotspot &amp; tethering status</string>
</resources>
 No newline at end of file
+10 −110
Original line number Diff line number Diff line
@@ -56,10 +56,8 @@ import static android.net.wifi.WifiManager.WIFI_AP_STATE_DISABLED;
import static android.telephony.CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import static com.android.server.connectivity.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE;

import android.app.usage.NetworkStatsManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothPan;
@@ -69,7 +67,6 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager;
import android.net.EthernetManager;
@@ -125,7 +122,6 @@ import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.MessageUtils;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.networkstack.tethering.R;

import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -221,14 +217,13 @@ public class Tethering {
    private final ActiveDataSubIdListener mActiveDataSubIdListener;
    private final ConnectedClientsTracker mConnectedClientsTracker;
    private final TetheringThreadExecutor mExecutor;
    private final TetheringNotificationUpdater mNotificationUpdater;
    private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
    // All the usage of mTetheringEventCallback should run in the same thread.
    private ITetheringEventCallback mTetheringEventCallback = null;

    private volatile TetheringConfiguration mConfig;
    private InterfaceSet mCurrentUpstreamIfaceSet;
    private Notification.Builder mTetheredNotificationBuilder;
    private int mLastNotificationId;

    private boolean mRndisEnabled;       // track the RNDIS function enabled state
    // True iff. WiFi tethering should be started when soft AP is ready.
@@ -251,6 +246,7 @@ public class Tethering {
        mContext = mDeps.getContext();
        mNetd = mDeps.getINetd(mContext);
        mLooper = mDeps.getTetheringLooper();
        mNotificationUpdater = mDeps.getNotificationUpdater(mContext);

        mPublicSync = new Object();

@@ -734,13 +730,10 @@ public class Tethering {
        final ArrayList<String> erroredList = new ArrayList<>();
        final ArrayList<Integer> lastErrorList = new ArrayList<>();

        boolean wifiTethered = false;
        boolean usbTethered = false;
        boolean bluetoothTethered = false;

        final TetheringConfiguration cfg = mConfig;
        mTetherStatesParcel = new TetherStatesParcel();

        int downstreamTypesMask = DOWNSTREAM_NONE;
        synchronized (mPublicSync) {
            for (int i = 0; i < mTetherStates.size(); i++) {
                TetherState tetherState = mTetherStates.valueAt(i);
@@ -754,11 +747,11 @@ public class Tethering {
                    localOnlyList.add(iface);
                } else if (tetherState.lastState == IpServer.STATE_TETHERED) {
                    if (cfg.isUsb(iface)) {
                        usbTethered = true;
                        downstreamTypesMask |= (1 << TETHERING_USB);
                    } else if (cfg.isWifi(iface)) {
                        wifiTethered = true;
                        downstreamTypesMask |= (1 << TETHERING_WIFI);
                    } else if (cfg.isBluetooth(iface)) {
                        bluetoothTethered = true;
                        downstreamTypesMask |= (1 << TETHERING_BLUETOOTH);
                    }
                    tetherList.add(iface);
                }
@@ -792,98 +785,7 @@ public class Tethering {
                    "error", TextUtils.join(",", erroredList)));
        }

        if (usbTethered) {
            if (wifiTethered || bluetoothTethered) {
                showTetheredNotification(R.drawable.stat_sys_tether_general);
            } else {
                showTetheredNotification(R.drawable.stat_sys_tether_usb);
            }
        } else if (wifiTethered) {
            if (bluetoothTethered) {
                showTetheredNotification(R.drawable.stat_sys_tether_general);
            } else {
                /* We now have a status bar icon for WifiTethering, so drop the notification */
                clearTetheredNotification();
            }
        } else if (bluetoothTethered) {
            showTetheredNotification(R.drawable.stat_sys_tether_bluetooth);
        } else {
            clearTetheredNotification();
        }
    }

    private void showTetheredNotification(int id) {
        showTetheredNotification(id, true);
    }

    @VisibleForTesting
    protected void showTetheredNotification(int id, boolean tetheringOn) {
        NotificationManager notificationManager =
                (NotificationManager) mContext.createContextAsUser(UserHandle.ALL, 0)
                        .getSystemService(Context.NOTIFICATION_SERVICE);
        if (notificationManager == null) {
            return;
        }
        final NotificationChannel channel = new NotificationChannel(
                "TETHERING_STATUS",
                mContext.getResources().getString(R.string.notification_channel_tethering_status),
                NotificationManager.IMPORTANCE_LOW);
        notificationManager.createNotificationChannel(channel);

        if (mLastNotificationId != 0) {
            if (mLastNotificationId == id) {
                return;
            }
            notificationManager.cancel(null, mLastNotificationId);
            mLastNotificationId = 0;
        }

        Intent intent = new Intent();
        intent.setClassName("com.android.settings", "com.android.settings.TetherSettings");
        intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);

        PendingIntent pi = PendingIntent.getActivity(
                mContext.createContextAsUser(UserHandle.CURRENT, 0), 0, intent, 0, null);

        Resources r = mContext.getResources();
        final CharSequence title;
        final CharSequence message;

        if (tetheringOn) {
            title = r.getText(R.string.tethered_notification_title);
            message = r.getText(R.string.tethered_notification_message);
        } else {
            title = r.getText(R.string.disable_tether_notification_title);
            message = r.getText(R.string.disable_tether_notification_message);
        }

        if (mTetheredNotificationBuilder == null) {
            mTetheredNotificationBuilder = new Notification.Builder(mContext, channel.getId());
            mTetheredNotificationBuilder.setWhen(0)
                    .setOngoing(true)
                    .setColor(mContext.getColor(
                            android.R.color.system_notification_accent_color))
                    .setVisibility(Notification.VISIBILITY_PUBLIC)
                    .setCategory(Notification.CATEGORY_STATUS);
        }
        mTetheredNotificationBuilder.setSmallIcon(id)
                .setContentTitle(title)
                .setContentText(message)
                .setContentIntent(pi);
        mLastNotificationId = id;

        notificationManager.notify(null, mLastNotificationId, mTetheredNotificationBuilder.build());
    }

    @VisibleForTesting
    protected void clearTetheredNotification() {
        NotificationManager notificationManager =
                (NotificationManager) mContext.createContextAsUser(UserHandle.ALL, 0)
                        .getSystemService(Context.NOTIFICATION_SERVICE);
        if (notificationManager != null && mLastNotificationId != 0) {
            notificationManager.cancel(null, mLastNotificationId);
            mLastNotificationId = 0;
        }
        mNotificationUpdater.onDownstreamChanged(downstreamTypesMask);
    }

    private class StateReceiver extends BroadcastReceiver {
@@ -1077,12 +979,10 @@ public class Tethering {
                return;
            }

            mWrapper.clearTetheredNotification();
            // TODO: Add user restrictions notification.
            final boolean isTetheringActiveOnDevice = (mWrapper.getTetheredIfaces().length != 0);

            if (newlyDisallowed && isTetheringActiveOnDevice) {
                mWrapper.showTetheredNotification(
                        R.drawable.stat_sys_tether_general, false);
                mWrapper.untetherAll();
                // TODO(b/148139325): send tetheringSupported on restriction change
            }
+9 −0
Original line number Diff line number Diff line
@@ -26,6 +26,8 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;

import androidx.annotation.NonNull;

import com.android.internal.util.StateMachine;

import java.util.ArrayList;
@@ -101,6 +103,13 @@ public abstract class TetheringDependencies {
                (IBinder) context.getSystemService(Context.NETD_SERVICE));
    }

    /**
     * Get a reference to the TetheringNotificationUpdater to be used by tethering.
     */
    public TetheringNotificationUpdater getNotificationUpdater(@NonNull final Context ctx) {
        return new TetheringNotificationUpdater(ctx);
    }

    /**
     * Get tethering thread looper.
     */
Loading