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(); + } + } +}