diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 33361721dea40f9bc504c5d535f0b4db0285c51f..4c4872dd7d5c537bf56fdba11c1ff53f2a8e2b87 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10678,6 +10678,60 @@ public final class Settings {
          */
         public static final String PROXIMITY_ON_WAKE = "proximity_on_wake";
 
+        /**
+         * Network traffic indicator location
+         * 0 = Disabled
+         * 1 = Statusbar
+         * 2 = Quick statusbar
+         * @hide
+         */
+        public static final String NETWORK_TRAFFIC_LOCATION = "network_traffic_location";
+
+        /**
+         * Network traffic indicator mode
+         * 0 = Display both up- and down-stream traffic
+         * 1 = Display up-stream traffic only
+         * 2 = Display down-stream traffic only
+         * @hide
+         */
+        public static final String NETWORK_TRAFFIC_MODE = "network_traffic_mode";
+
+        /**
+         * Whether or not to hide the network traffic indicator when there is no activity
+         * @hide
+         */
+        public static final String NETWORK_TRAFFIC_AUTOHIDE = "network_traffic_autohide";
+
+        /**
+         * Threshold below which network traffic would be hidden
+         * @hide
+         */
+        public static final String NETWORK_TRAFFIC_AUTOHIDE_THRESHOLD = "network_traffic_autohide_threshold";
+
+        /**
+         * Measurement unit preference for network traffic
+         * @hide
+         */
+        public static final String NETWORK_TRAFFIC_UNITS = "network_traffic_units";
+
+        /**
+         * Whether or not to show measurement units in the network traffic indiciator
+         * @hide
+         */
+        public static final String NETWORK_TRAFFIC_SHOW_UNITS = "network_traffic_show_units";
+
+        /**
+         * Specify refresh duration for network traffic
+         * @hide
+         */
+        public static final String NETWORK_TRAFFIC_REFRESH_INTERVAL = "network_traffic_refresh_interval";
+
+        /**
+         * Whether to hide arrows for network traffic
+         * @hide
+         */
+        public static final String NETWORK_TRAFFIC_HIDEARROW = "network_traffic_hidearrow";
+
         /**
          * Keys we no longer back up under the current schema, but want to continue to
          * process when restoring historical backup datasets.
diff --git a/packages/SystemUI/LMODroidManifest.xml b/packages/SystemUI/LMODroidManifest.xml
index 127887ad77e302d0151c272b190de14f02632763..524de5d259fd470f2bb73ab96684fb690f34c823 100644
--- a/packages/SystemUI/LMODroidManifest.xml
+++ b/packages/SystemUI/LMODroidManifest.xml
@@ -19,6 +19,9 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.systemui">
 
+    <!-- Network traffic monitor -->
+    <uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK" />
+
     <!-- QS Tiles -->
     <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
     <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
diff --git a/packages/SystemUI/res/drawable/stat_sys_network_traffic.xml b/packages/SystemUI/res/drawable/stat_sys_network_traffic.xml
new file mode 100644
index 0000000000000000000000000000000000000000..36648e1177f113fc3383c59379c56085cdfacb39
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_network_traffic.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019-2020 crDroid Android 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="14dp"
+    android:height="18dp"
+    android:viewportWidth="14"
+    android:viewportHeight="18">
+
+    <path
+        android:name="down"
+        android:fillColor="#4DFFFFFF"
+        android:pathData="M9,16l3-3h-2V8H8v5H6L9,16z" />
+
+    <path
+        android:name="up"
+        android:fillColor="#4DFFFFFF"
+        android:pathData="M5,2L2,5h2v5h2V5h2L5,2z" />
+
+</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_network_traffic_down.xml b/packages/SystemUI/res/drawable/stat_sys_network_traffic_down.xml
new file mode 100644
index 0000000000000000000000000000000000000000..cbdd839af23ecbcfbce86fb05a6df45bba34f116
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_network_traffic_down.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019-2020 crDroid Android 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="14dp"
+    android:height="18dp"
+    android:viewportWidth="14"
+    android:viewportHeight="18">
+
+    <path
+        android:name="down"
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M9,16l3-3h-2V8H8v5H6L9,16z" />
+
+    <path
+        android:name="up"
+        android:fillColor="#4DFFFFFF"
+        android:pathData="M5,2L2,5h2v5h2V5h2L5,2z" />
+
+</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_network_traffic_up.xml b/packages/SystemUI/res/drawable/stat_sys_network_traffic_up.xml
new file mode 100644
index 0000000000000000000000000000000000000000..869a486700cc32890349acb669dd01bf525bf485
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_network_traffic_up.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019-2020 crDroid Android 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="14dp"
+    android:height="18dp"
+    android:viewportWidth="14"
+    android:viewportHeight="18">
+
+    <path
+        android:name="down"
+        android:fillColor="#4DFFFFFF"
+        android:pathData="M9,16l3-3h-2V8H8v5H6L9,16z" />
+
+    <path
+        android:name="up"
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M5,2L2,5h2v5h2V5h2L5,2z" />
+
+</vector>
diff --git a/packages/SystemUI/res/drawable/stat_sys_network_traffic_updown.xml b/packages/SystemUI/res/drawable/stat_sys_network_traffic_updown.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f1b1257c8f7c1e4fa51960a4b6ddf74b9b8a136b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/stat_sys_network_traffic_updown.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019-2020 crDroid Android 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="14dp"
+    android:height="18dp"
+    android:viewportWidth="14"
+    android:viewportHeight="18">
+
+    <path
+        android:name="down"
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M9,16l3-3h-2V8H8v5H6L9,16z" />
+
+    <path
+        android:name="up"
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M5,2L2,5h2v5h2V5h2L5,2z" />
+
+</vector>
diff --git a/packages/SystemUI/res/layout/quick_qs_status_icons.xml b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
index 542a1c9d22bd74e16b025a28411cccef7aed3c09..9ee1a228354c167507823458b9d9f2ab773c2c54 100644
--- a/packages/SystemUI/res/layout/quick_qs_status_icons.xml
+++ b/packages/SystemUI/res/layout/quick_qs_status_icons.xml
@@ -105,6 +105,12 @@
             systemui:textAppearance="@style/TextAppearance.QS.Status"
             android:paddingEnd="2dp" />
 
+        <com.android.systemui.statusbar.policy.NetworkTraffic
+            android:id="@+id/network_traffic"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:visibility="gone" />
+
     </LinearLayout>
     </FrameLayout>
 
diff --git a/packages/SystemUI/res/values/lmodroid_strings.xml b/packages/SystemUI/res/values/lmodroid_strings.xml
index 917dbeafe39e6fe18485bc964698c7115806a7a9..fce552f41fac534fd8bbbc131a804df568291f8c 100644
--- a/packages/SystemUI/res/values/lmodroid_strings.xml
+++ b/packages/SystemUI/res/values/lmodroid_strings.xml
@@ -89,4 +89,12 @@
 
     <!-- USB tethering QS tile -->
     <string name="quick_settings_usb_tether_label">USB tethering</string>
+
+    <!-- Status bar network traffic monitor strings -->
+    <string name="kilobitspersecond_short">kb/s</string>
+    <string name="megabitspersecond_short">Mb/s</string>
+    <string name="gigabitspersecond_short">Gb/s</string>
+    <string name="kilobytespersecond_short">kB/s</string>
+    <string name="megabytespersecond_short">MB/s</string>
+    <string name="gigabytespersecond_short">GB/s</string>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 48fe77482340b623941024a2adc492cbd4ed88b1..5f508ffee69340b8720c30cf628ccd7aed873d1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone;
 
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_ICON;
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
+import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_NETWORK_TRAFFIC;
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
 
 import android.annotation.Nullable;
@@ -47,6 +48,7 @@ import com.android.systemui.statusbar.StatusIconDisplayable;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.CallIndicatorIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import com.android.systemui.statusbar.policy.StatusBarNetworkTraffic;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -304,6 +306,9 @@ public interface StatusBarIconController {
 
                 case TYPE_MOBILE:
                     return addMobileIcon(index, slot, holder.getMobileState());
+
+                case TYPE_NETWORK_TRAFFIC:
+                    return addNetworkTraffic(index, slot);
             }
 
             return null;
@@ -330,6 +335,12 @@ public interface StatusBarIconController {
             return view;
         }
 
+        protected StatusBarNetworkTraffic addNetworkTraffic(int index, String slot) {
+            StatusBarNetworkTraffic view = onCreateNetworkTraffic(slot);
+            mGroup.addView(view, index, onCreateLayoutParams());
+            return view;
+        }
+
         @VisibleForTesting
         protected StatusBarMobileView addMobileIcon(int index, String slot, MobileIconState state) {
             StatusBarMobileView view = onCreateStatusBarMobileView(slot);
@@ -357,6 +368,11 @@ public interface StatusBarIconController {
             return view;
         }
 
+        private StatusBarNetworkTraffic onCreateNetworkTraffic(String slot) {
+            StatusBarNetworkTraffic view = new StatusBarNetworkTraffic(mContext);
+            return view;
+        }
+
         protected LinearLayout.LayoutParams onCreateLayoutParams() {
             return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
         }
@@ -398,8 +414,11 @@ public interface StatusBarIconController {
         }
 
         public void onSetIcon(int viewIndex, StatusBarIcon icon) {
-            StatusBarIconView view = (StatusBarIconView) mGroup.getChildAt(viewIndex);
-            view.set(icon);
+            View view = mGroup.getChildAt(viewIndex);
+            if (view instanceof StatusBarIconView) {
+                ((StatusBarIconView) view).set(icon);
+            }
+
         }
 
         public void onSetIconHolder(int viewIndex, StatusBarIconHolder holder) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index af342dd31a764d5383a32fbb197d4676e0e2ca15..603c0659a80a01955f1a392780dc64ca087bb77c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -33,6 +33,7 @@ public class StatusBarIconHolder {
     public static final int TYPE_ICON = 0;
     public static final int TYPE_WIFI = 1;
     public static final int TYPE_MOBILE = 2;
+    public static final int TYPE_NETWORK_TRAFFIC = 42;
 
     private StatusBarIcon mIcon;
     private WifiIconState mWifiState;
@@ -95,6 +96,12 @@ public class StatusBarIconHolder {
         return holder;
     }
 
+    public static StatusBarIconHolder fromNetworkTraffic() {
+        StatusBarIconHolder holder = new StatusBarIconHolder();
+        holder.mType = TYPE_NETWORK_TRAFFIC;
+        return holder;
+    }
+
     public int getType() {
         return mType;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
index c876c3228eb87487b5f019a88c65a9d0ec44caaa..694d7ecbb7f263460fbf7828021b2bcc7ce4c111 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
@@ -22,6 +22,7 @@ import android.annotation.NonNull;
 import android.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.statusbar.policy.StatusBarNetworkTraffic;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -32,6 +33,9 @@ public class StatusBarIconList {
 
     public StatusBarIconList(String[] slots) {
         final int N = slots.length;
+        // Network traffic slot
+        mSlots.add(0, new Slot(StatusBarNetworkTraffic.SLOT,
+                    StatusBarIconHolder.fromNetworkTraffic()));
         for (int i=0; i < N; i++) {
             mSlots.add(new Slot(slots[i], null));
         }
@@ -45,9 +49,9 @@ public class StatusBarIconList {
                 return i;
             }
         }
-        // Auto insert new items at the beginning.
-        mSlots.add(0, new Slot(slot, null));
-        return 0;
+        // Auto insert new items behind network traffic.
+        mSlots.add(1, new Slot(slot, null));
+        return 1;
     }
 
     protected ArrayList<Slot> getSlots() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
index 329293409dc200fad3ce8785f22d7f457dad80bb..3d576ac1653921b76796422de6aeaef247b2aa45 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
@@ -456,6 +456,12 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout {
                 animationProperties = X_ANIMATION_PROPERTIES;
             }
 
+            // HACK: we dont want networktraffic to animate
+            if (icon.getSlot() == "networktraffic") {
+                animateVisibility = false;
+                animationProperties = null;
+            }
+
             icon.setVisibleState(visibleState, animateVisibility);
             if (animationProperties != null) {
                 animateTo(view, animationProperties);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkTraffic.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkTraffic.java
new file mode 100644
index 0000000000000000000000000000000000000000..cbf54650b8432e33929d203cd0f17eefc2b2b32b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkTraffic.java
@@ -0,0 +1,512 @@
+/**
+ * Copyright (C) 2019-2020 crDroid Android 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.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.Typeface;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.NetworkStats;
+import android.net.TrafficStats;
+import android.os.Handler;
+import android.os.INetworkManagementService;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.Spanned;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.style.RelativeSizeSpan;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.tuner.TunerService;
+
+import java.text.DecimalFormat;
+import java.util.HashMap;
+
+public class NetworkTraffic extends TextView implements TunerService.Tunable {
+    private static final String TAG = "NetworkTraffic";
+
+    private static final int MODE_UPSTREAM_AND_DOWNSTREAM = 0;
+    private static final int MODE_UPSTREAM_ONLY = 1;
+    private static final int MODE_DOWNSTREAM_ONLY = 2;
+
+    protected static final int LOCATION_DISABLED = 0;
+    protected static final int LOCATION_STATUSBAR = 1;
+    protected static final int LOCATION_QUICK_STATUSBAR = 2;
+
+    private static final int MESSAGE_TYPE_PERIODIC_REFRESH = 0;
+    private static final int MESSAGE_TYPE_UPDATE_VIEW = 1;
+
+    private static final int Kilo = 1000;
+    private static final int Mega = Kilo * Kilo;
+    private static final int Giga = Mega * Kilo;
+
+    private static final String NETWORK_TRAFFIC_LOCATION =
+            "customsecure:" + Settings.Secure.NETWORK_TRAFFIC_LOCATION;
+    private static final String NETWORK_TRAFFIC_MODE =
+            "customsecure:" + Settings.Secure.NETWORK_TRAFFIC_MODE;
+    private static final String NETWORK_TRAFFIC_AUTOHIDE =
+            "customsecure:" + Settings.Secure.NETWORK_TRAFFIC_AUTOHIDE;
+    private static final String NETWORK_TRAFFIC_AUTOHIDE_THRESHOLD =
+            "customsecure:" + Settings.Secure.NETWORK_TRAFFIC_AUTOHIDE_THRESHOLD;
+    private static final String NETWORK_TRAFFIC_UNITS =
+            "customsecure:" + Settings.Secure.NETWORK_TRAFFIC_UNITS;
+    private static final String NETWORK_TRAFFIC_REFRESH_INTERVAL =
+            "customsecure:" + Settings.Secure.NETWORK_TRAFFIC_REFRESH_INTERVAL;
+    private static final String NETWORK_TRAFFIC_HIDEARROW =
+            "customsecure:" + Settings.Secure.NETWORK_TRAFFIC_HIDEARROW;
+
+    protected int mLocation = LOCATION_DISABLED;
+    private int mMode = MODE_UPSTREAM_AND_DOWNSTREAM;
+    private int mSubMode = MODE_UPSTREAM_AND_DOWNSTREAM;
+    protected boolean mIsActive;
+    private boolean mTrafficActive;
+    private long mLastTxBytes;
+    private long mLastRxBytes;
+    private long mLastUpdateTime;
+    private boolean mAutoHide;
+    private long mAutoHideThreshold;
+    private int mUnits;
+    protected int mIconTint = 0;
+    protected int newTint = Color.WHITE;
+
+    private Drawable mDrawable;
+
+    private int mRefreshInterval = 2;
+
+    protected boolean mAttached;
+    private boolean mHideArrows;
+
+    private INetworkManagementService mNetworkManagementService;
+
+    protected boolean mVisible = true;
+    protected boolean mScreenOn = true;
+
+    private ConnectivityManager mConnectivityManager;
+
+    private RelativeSizeSpan mSpeedRelativeSizeSpan = new RelativeSizeSpan(0.70f);
+    private RelativeSizeSpan mUnitRelativeSizeSpan = new RelativeSizeSpan(0.65f);
+
+    protected boolean mEnabled = false;
+    protected boolean mConnectionAvailable = true;
+
+    public NetworkTraffic(Context context) {
+        this(context, null);
+    }
+
+    public NetworkTraffic(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public NetworkTraffic(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mContext = context;
+        mConnectivityManager =
+                (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        if (!mAttached) {
+            mAttached = true;
+            final TunerService tunerService = Dependency.get(TunerService.class);
+            tunerService.addTunable(this, NETWORK_TRAFFIC_LOCATION);
+            tunerService.addTunable(this, NETWORK_TRAFFIC_MODE);
+            tunerService.addTunable(this, NETWORK_TRAFFIC_AUTOHIDE);
+            tunerService.addTunable(this, NETWORK_TRAFFIC_AUTOHIDE_THRESHOLD);
+            tunerService.addTunable(this, NETWORK_TRAFFIC_UNITS);
+            tunerService.addTunable(this, NETWORK_TRAFFIC_REFRESH_INTERVAL);
+            tunerService.addTunable(this, NETWORK_TRAFFIC_HIDEARROW);
+
+            mConnectionAvailable = mConnectivityManager.getActiveNetworkInfo() != null;
+
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+            filter.addAction(Intent.ACTION_SCREEN_OFF);
+            filter.addAction(Intent.ACTION_SCREEN_ON);
+            mContext.registerReceiver(mIntentReceiver, filter, null, mTrafficHandler);
+        }
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (mAttached) {
+            mContext.unregisterReceiver(mIntentReceiver);
+            Dependency.get(TunerService.class).removeTunable(this);
+            mAttached = false;
+        }
+    }
+
+    private Handler mTrafficHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            long rxBytes = 0;
+            long txBytes = 0;
+
+            if (msg.what == MESSAGE_TYPE_PERIODIC_REFRESH) {
+                final long now = SystemClock.elapsedRealtime();
+                long timeDelta = now - mLastUpdateTime; /* ms */
+
+                if (timeDelta >= mRefreshInterval * 1000 * 0.95f) {
+                    long[] newTotalRxTxBytes = getTotalRxTxBytes();
+
+                    final long rxBytesDelta = newTotalRxTxBytes[0] - mLastRxBytes;
+                    final long txBytesDelta = newTotalRxTxBytes[1] - mLastTxBytes;
+
+                    rxBytes = (long) (rxBytesDelta / (timeDelta / 1000f));
+                    txBytes = (long) (txBytesDelta / (timeDelta / 1000f));
+
+                    mLastRxBytes = newTotalRxTxBytes[0];
+                    mLastTxBytes = newTotalRxTxBytes[1];
+                    mLastUpdateTime = now;
+                }
+            }
+
+            final boolean showUpstream =
+                    mMode == MODE_UPSTREAM_ONLY || mMode == MODE_UPSTREAM_AND_DOWNSTREAM;
+            final boolean showDownstream =
+                    mMode == MODE_DOWNSTREAM_ONLY || mMode == MODE_UPSTREAM_AND_DOWNSTREAM;
+            final boolean aboveThreshold = (showUpstream && txBytes > mAutoHideThreshold)
+                    || (showDownstream && rxBytes > mAutoHideThreshold);
+            mIsActive = mAttached && mConnectionAvailable && (!mAutoHide || aboveThreshold);
+            int submode = MODE_UPSTREAM_AND_DOWNSTREAM;
+            final boolean trafficactive = (txBytes > 0 || rxBytes > 0);
+
+            clearHandlerCallbacks();
+
+            if (mEnabled && mIsActive) {
+                CharSequence output = "";
+                if (showUpstream && showDownstream) {
+                    if (txBytes > rxBytes) {
+                        output = formatOutput(txBytes);
+                        submode = MODE_UPSTREAM_ONLY;
+                    } else if (txBytes < rxBytes) {
+                        output = formatOutput(rxBytes);
+                        submode = MODE_DOWNSTREAM_ONLY;
+                    } else {
+                        output = formatOutput(rxBytes);
+                        submode = MODE_UPSTREAM_AND_DOWNSTREAM;
+                    }
+                } else if (showDownstream) {
+                    output = formatOutput(rxBytes);
+                } else if (showUpstream) {
+                    output = formatOutput(txBytes);
+                }
+
+                // Update view if there's anything new to show
+                if (output != getText()) {
+                    setText(output);
+                }
+            }
+
+            updateVisibility();
+
+            if (mVisible && (mSubMode != submode ||
+                    mTrafficActive != trafficactive)) {
+                mSubMode = submode;
+                mTrafficActive = trafficactive;
+                setTrafficDrawable();
+            }
+
+            // Schedule periodic refresh
+            if (mEnabled && mScreenOn && mAttached) {
+                mTrafficHandler.sendEmptyMessageDelayed(MESSAGE_TYPE_PERIODIC_REFRESH,
+                        mRefreshInterval * 1000);
+            }
+        }
+
+        private CharSequence formatOutput(long speed) {
+            DecimalFormat decimalFormat;
+            String unit;
+            String formatSpeed;
+            SpannableString spanUnitString;
+            SpannableString spanSpeedString;
+            String gunit, munit, kunit;
+
+            if (mUnits == 0) {
+                // speed is in bytes, convert to bits
+                speed = speed * 8;
+                gunit = mContext.getString(R.string.gigabitspersecond_short);
+                munit = mContext.getString(R.string.megabitspersecond_short);
+                kunit = mContext.getString(R.string.kilobitspersecond_short);
+            } else {
+                gunit = mContext.getString(R.string.gigabytespersecond_short);
+                munit = mContext.getString(R.string.megabytespersecond_short);
+                kunit = mContext.getString(R.string.kilobytespersecond_short);
+            }
+
+            if (speed >= Giga) {
+                unit = gunit;
+                decimalFormat = new DecimalFormat("0.##");
+                formatSpeed = decimalFormat.format(speed / (float)Giga);
+            } else if (speed >= 100 * Mega) {
+                decimalFormat = new DecimalFormat("##0");
+                unit = munit;
+                formatSpeed = decimalFormat.format(speed / (float)Mega);
+            } else if (speed >= 10 * Mega) {
+                decimalFormat = new DecimalFormat("#0.#");
+                unit = munit;
+                formatSpeed = decimalFormat.format(speed / (float)Mega);
+            } else if (speed >= Mega) {
+                decimalFormat = new DecimalFormat("0.##");
+                unit = munit;
+                formatSpeed = decimalFormat.format(speed / (float)Mega);
+            } else if (speed >= 100 * Kilo) {
+                decimalFormat = new DecimalFormat("##0");
+                unit = kunit;
+                formatSpeed = decimalFormat.format(speed / (float)Kilo);
+            } else if (speed >= 10 * Kilo) {
+                decimalFormat = new DecimalFormat("#0.#");
+                unit = kunit;
+                formatSpeed = decimalFormat.format(speed / (float)Kilo);
+            } else {
+                decimalFormat = new DecimalFormat("0.##");
+                unit = kunit;
+                formatSpeed = decimalFormat.format(speed / (float)Kilo);
+            }
+            spanSpeedString = new SpannableString(formatSpeed);
+            spanSpeedString.setSpan(mSpeedRelativeSizeSpan, 0, (formatSpeed).length(),
+                    Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+            spanUnitString = new SpannableString(unit);
+            spanUnitString.setSpan(mUnitRelativeSizeSpan, 0, (unit).length(),
+                    Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+            return TextUtils.concat(spanSpeedString, "\n", spanUnitString);
+        }
+
+        private long[] getTotalRxTxBytes() {
+            long[] bytes = new long[] { 0, 0 };
+
+            // Sum tx and rx bytes from all sources of interest
+            // Add stats
+            bytes[0] = TrafficStats.getTotalRxBytes();
+            bytes[1] = TrafficStats.getTotalTxBytes();
+
+            // Add tether hw offload counters since these are
+            // not included in netd interface stats.
+            final TetheringStats tetheringStats = getOffloadTetheringStats();
+            bytes[0] += tetheringStats.rxBytes;
+            bytes[1] += tetheringStats.txBytes;
+
+            return bytes;
+        }
+    };
+
+    protected void setEnabled() {
+        mEnabled = mLocation == LOCATION_QUICK_STATUSBAR;
+    }
+
+    protected void updateVisibility() {
+        boolean visible = mEnabled && mIsActive && getText() != "";
+        if (visible != mVisible) {
+            mVisible = visible;
+            setVisibility(mVisible ? VISIBLE : GONE);
+        }
+    }
+
+    private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action == null) return;
+            if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
+                mConnectionAvailable = mConnectivityManager.getActiveNetworkInfo() != null;
+                if (mScreenOn) updateViews();
+            } else if (action.equals(Intent.ACTION_SCREEN_ON)) {
+                mScreenOn = true;
+                updateViews();
+            } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+                mScreenOn = false;
+                clearHandlerCallbacks();
+            }
+        }
+    };
+
+    private class TetheringStats {
+        long txBytes;
+        long rxBytes;
+    }
+
+    private TetheringStats getOffloadTetheringStats() {
+        TetheringStats tetheringStats = new TetheringStats();
+
+        NetworkStats stats = null;
+
+        if (mNetworkManagementService == null) {
+            mNetworkManagementService = INetworkManagementService.Stub.asInterface(
+                    ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
+        }
+
+        try {
+            // STATS_PER_UID returns hw offload and netd stats combined (as entry UID_TETHERING)
+            // STATS_PER_IFACE returns only hw offload stats (as entry UID_ALL)
+            stats = mNetworkManagementService.getNetworkStatsTethering(
+                    NetworkStats.STATS_PER_IFACE);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Unable to call getNetworkStatsTethering: " + e);
+        }
+        if (stats == null) {
+            // nothing we can do except return zero stats
+            return tetheringStats;
+        }
+
+        NetworkStats.Entry entry = null;
+        // Entries here are per tethered interface.
+        // Counters persist even after tethering has been disabled.
+        for (int i = 0; i < stats.size(); i++) {
+            entry = stats.getValues(i, entry);
+            // hw offload tether stats are reported under UID_ALL.
+            if (entry.uid == NetworkStats.UID_ALL) {
+                tetheringStats.txBytes += entry.txBytes;
+                tetheringStats.rxBytes += entry.rxBytes;
+            }
+        }
+        return tetheringStats;
+    }
+
+    @Override
+    public void onTuningChanged(String key, String newValue) {
+        switch (key) {
+            case NETWORK_TRAFFIC_LOCATION:
+                mLocation =
+                        TunerService.parseInteger(newValue, 0);
+                setEnabled();
+                if (mEnabled) {
+                    setLines(2);
+                    String txtFont = getResources().getString(com.android.internal.R.string.config_bodyFontFamily);
+                    setTypeface(Typeface.create(txtFont, Typeface.BOLD));
+                    setLineSpacing(0.80f, 0.80f);
+                }
+                updateViews();
+                break;
+            case NETWORK_TRAFFIC_MODE:
+                mMode =
+                        TunerService.parseInteger(newValue, 0);
+                updateViews();
+                setTrafficDrawable();
+                break;
+            case NETWORK_TRAFFIC_AUTOHIDE:
+                mAutoHide =
+                        TunerService.parseIntegerSwitch(newValue, false);
+                updateViews();
+                break;
+            case NETWORK_TRAFFIC_AUTOHIDE_THRESHOLD:
+                int autohidethreshold =
+                        TunerService.parseInteger(newValue, 0);
+                mAutoHideThreshold = autohidethreshold * Kilo; /* Convert kB to Bytes */
+                updateViews();
+                break;
+            case NETWORK_TRAFFIC_UNITS:
+                mUnits =
+                        TunerService.parseInteger(newValue, 1);
+                updateViews();
+                break;
+            case NETWORK_TRAFFIC_REFRESH_INTERVAL:
+                mRefreshInterval =
+                        TunerService.parseInteger(newValue, 2);
+                updateViews();
+                break;
+            case NETWORK_TRAFFIC_HIDEARROW:
+                mHideArrows =
+                        TunerService.parseIntegerSwitch(newValue, false);
+                if (!mHideArrows) {
+                    setGravity(Gravity.END|Gravity.CENTER_VERTICAL);
+                } else {
+                    setGravity(Gravity.CENTER);
+                }
+                setTrafficDrawable();
+                break;
+            default:
+                break;
+        }
+    }
+
+    protected void updateViews() {
+        if (mEnabled) {
+            updateViewState();
+        }
+    }
+
+    private void updateViewState() {
+        mTrafficHandler.removeMessages(MESSAGE_TYPE_UPDATE_VIEW);
+        mTrafficHandler.sendEmptyMessageDelayed(MESSAGE_TYPE_UPDATE_VIEW, 1000);
+    }
+
+    private void clearHandlerCallbacks() {
+        mTrafficHandler.removeMessages(MESSAGE_TYPE_PERIODIC_REFRESH);
+        mTrafficHandler.removeMessages(MESSAGE_TYPE_UPDATE_VIEW);
+    }
+
+    private void setTrafficDrawable() {
+        final int drawableResId;
+        final Drawable drawable;
+
+        if (mHideArrows) {
+            drawableResId = 0;
+        } else if (!mTrafficActive) {
+            drawableResId = R.drawable.stat_sys_network_traffic;
+        } else if (mMode == MODE_UPSTREAM_ONLY || mSubMode == MODE_UPSTREAM_ONLY) {
+            drawableResId = R.drawable.stat_sys_network_traffic_up;
+        } else if (mMode == MODE_DOWNSTREAM_ONLY || mSubMode == MODE_DOWNSTREAM_ONLY) {
+            drawableResId = R.drawable.stat_sys_network_traffic_down;
+        } else if (mMode == MODE_UPSTREAM_AND_DOWNSTREAM) {
+            drawableResId = R.drawable.stat_sys_network_traffic_updown;
+        } else {
+            drawableResId = 0;
+        }
+        drawable = drawableResId != 0 ? getResources().getDrawable(drawableResId) : null;
+        if (mDrawable != drawable || mIconTint != newTint) {
+            mDrawable = drawable;
+            mIconTint = newTint;
+            setCompoundDrawablesWithIntrinsicBounds(null, null, mDrawable, null);
+            updateTrafficDrawable();
+        }
+    }
+
+    protected void updateTrafficDrawable() {
+        if (mDrawable != null) {
+            mDrawable.setColorFilter(mIconTint, PorterDuff.Mode.MULTIPLY);
+        }
+        setTextColor(mIconTint);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/StatusBarNetworkTraffic.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/StatusBarNetworkTraffic.java
new file mode 100644
index 0000000000000000000000000000000000000000..c7401f5d65a20c2fb16bde4bc727b0e805b53853
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/StatusBarNetworkTraffic.java
@@ -0,0 +1,151 @@
+/**
+ * Copyright (C) 2019-2020 crDroid Android 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.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static com.android.systemui.statusbar.StatusBarIconView.STATE_DOT;
+import static com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN;
+import static com.android.systemui.statusbar.StatusBarIconView.STATE_ICON;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.plugins.DarkIconDispatcher;
+import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
+import com.android.systemui.statusbar.StatusIconDisplayable;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+
+
+/** @hide */
+public class StatusBarNetworkTraffic extends NetworkTraffic implements DarkReceiver,
+        StatusIconDisplayable {
+
+
+    public static final String SLOT = "networktraffic";
+
+    private int mVisibleState = -1;
+    private boolean mSystemIconVisible = true;
+    private boolean mColorIsStatic;
+
+    private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private boolean mKeyguardShowing;
+
+    public StatusBarNetworkTraffic(Context context) {
+        this(context, null);
+    }
+
+    public StatusBarNetworkTraffic(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public StatusBarNetworkTraffic(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        setVisibleState(STATE_ICON);
+
+        mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
+        mKeyguardUpdateMonitor.registerCallback(mUpdateCallback);
+    }
+
+    @Override
+    public void onDarkChanged(Rect area, float darkIntensity, int tint) {
+        if (mColorIsStatic) {
+            return;
+        }
+        newTint = DarkIconDispatcher.getTint(area, this, tint);
+        checkUpdateTrafficDrawable();
+    }
+
+    @Override
+    public void setStaticDrawableColor(int color) {
+        mColorIsStatic = true;
+        newTint = color;
+        checkUpdateTrafficDrawable();
+    }
+
+    @Override
+    public void setDecorColor(int color) {
+    }
+
+    @Override
+    public String getSlot() {
+        return SLOT;
+    }
+
+    @Override
+    public boolean isIconVisible() {
+        return mEnabled;
+    }
+
+    @Override
+    public int getVisibleState() {
+        return mVisibleState;
+    }
+
+    @Override
+    public void setVisibleState(int state, boolean animate) {
+        if (state == mVisibleState || !mEnabled || !mScreenOn) {
+            return;
+        }
+        mVisibleState = state;
+
+        switch (state) {
+            case STATE_ICON:
+                mSystemIconVisible = true;
+                break;
+            case STATE_DOT:
+            case STATE_HIDDEN:
+            default:
+                mSystemIconVisible = false;
+                break;
+        }
+    }
+
+    private final KeyguardUpdateMonitorCallback mUpdateCallback =
+            new KeyguardUpdateMonitorCallback() {
+                @Override
+                public void onKeyguardVisibilityChanged(boolean showing) {
+                    mKeyguardShowing = showing;
+                    updateVisibility();
+                }
+            };
+
+    @Override
+    protected void setEnabled() {
+        mEnabled = mLocation == LOCATION_STATUSBAR;
+    }
+
+    @Override
+    protected void updateVisibility() {
+        boolean visible = mEnabled && mIsActive && !mKeyguardShowing && mSystemIconVisible
+            && getText() != "";
+        if (visible != mVisible) {
+            mVisible = visible;
+            setVisibility(mVisible ? VISIBLE : GONE);
+            checkUpdateTrafficDrawable();
+        }
+    }
+
+    private void checkUpdateTrafficDrawable() {
+        // Wait for icon to be visible and tint to be changed
+        if (mVisible && mIconTint != newTint) {
+            mIconTint = newTint;
+            updateTrafficDrawable();
+        }
+    }
+}