From e4071127a62922cb662b7094572c6615d013edda Mon Sep 17 00:00:00 2001 From: Rohan Shah Date: Mon, 22 Jan 2018 15:16:09 -0800 Subject: [PATCH] [QS] Add secondary label to Hotspot tile Added info to callback and leveraged WifiManager API to get the number of connected devices. There's some minor state cleanup done and the fire callback mechanism was slightly updated. The secondary label is updated based on the connected devices & enabled state. Screenshot: https://screenshot.googleplex.com/jHMKmg2nO08 Test: manual Bug:68058038 Change-Id: Iafcebd3476d2c13e25939b45e78f32a5bd24cb68 --- packages/SystemUI/res/values/strings.xml | 5 + .../systemui/qs/tiles/HotspotTile.java | 54 ++++++++-- .../statusbar/phone/AutoTileManager.java | 2 +- .../statusbar/phone/PhoneStatusBarPolicy.java | 2 +- .../statusbar/policy/HotspotController.java | 6 +- .../policy/HotspotControllerImpl.java | 99 ++++++++++++++++--- packages/SystemUI/tests/AndroidManifest.xml | 1 + .../utils/leaks/FakeHotspotController.java | 5 + 8 files changed, 150 insertions(+), 24 deletions(-) diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 199ccfcaeaa2f..27ec8f785f67e 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -753,6 +753,11 @@ Tethering Hotspot + + + %d device + %d devices + Notifications diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java index 910b6b1740627..e1b58fe4c2d61 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java @@ -16,6 +16,7 @@ package com.android.systemui.qs.tiles; +import android.annotation.Nullable; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; @@ -37,7 +38,7 @@ import com.android.systemui.statusbar.policy.HotspotController; /** Quick settings tile: Hotspot **/ public class HotspotTile extends QSTileImpl { static final Intent TETHER_SETTINGS = new Intent().setComponent(new ComponentName( - "com.android.settings", "com.android.settings.TetherSettings")); + "com.android.settings", "com.android.settings.TetherSettings")); private final Icon mEnabledStatic = ResourceIcon.get(R.drawable.ic_hotspot); private final Icon mUnavailable = ResourceIcon.get(R.drawable.ic_hotspot_unavailable); @@ -115,11 +116,19 @@ public class HotspotTile extends QSTileImpl { state.label = mContext.getString(R.string.quick_settings_hotspot_label); checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_CONFIG_TETHERING); - if (arg instanceof Boolean) { - state.value = (boolean) arg; + + final int numConnectedDevices; + if (arg instanceof CallbackInfo) { + CallbackInfo info = (CallbackInfo) arg; + state.value = info.enabled; + numConnectedDevices = info.numConnectedDevices; } else { state.value = mController.isHotspotEnabled(); + numConnectedDevices = mController.getNumConnectedDevices(); } + + state.secondaryLabel = getSecondaryLabel(state.value, numConnectedDevices); + state.icon = mEnabledStatic; state.isAirplaneMode = mAirplaneMode.getValue() != 0; state.isTransient = mController.isHotspotTransient(); @@ -133,6 +142,18 @@ public class HotspotTile extends QSTileImpl { : state.value || state.isTransient ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE; } + @Nullable + private String getSecondaryLabel(boolean enabled, int numConnectedDevices) { + if (numConnectedDevices > 0 && enabled) { + return mContext.getResources().getQuantityString( + R.plurals.quick_settings_hotspot_num_devices, + numConnectedDevices, + numConnectedDevices); + } + + return null; + } + @Override public int getMetricsCategory() { return MetricsEvent.QS_HOTSPOT; @@ -148,9 +169,30 @@ public class HotspotTile extends QSTileImpl { } private final class Callback implements HotspotController.Callback { + final CallbackInfo mCallbackInfo = new CallbackInfo(); + @Override - public void onHotspotChanged(boolean enabled) { - refreshState(enabled); + public void onHotspotChanged(boolean enabled, int numConnectedDevices) { + mCallbackInfo.enabled = enabled; + mCallbackInfo.numConnectedDevices = numConnectedDevices; + refreshState(mCallbackInfo); } - }; + } + + /** + * Holder for any hotspot state info that needs to passed from the callback to + * {@link #handleUpdateState(State, Object)}. + */ + protected static final class CallbackInfo { + boolean enabled; + int numConnectedDevices; + + @Override + public String toString() { + return new StringBuilder("CallbackInfo[") + .append("enabled=").append(enabled) + .append(",numConnectedDevices=").append(numConnectedDevices) + .append(']').toString(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java index 149ec0b3a56aa..36f9f6b794173 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java @@ -127,7 +127,7 @@ public class AutoTileManager { private final HotspotController.Callback mHotspotCallback = new Callback() { @Override - public void onHotspotChanged(boolean enabled) { + public void onHotspotChanged(boolean enabled, int numDevices) { if (mAutoTracker.isAdded(HOTSPOT)) return; if (enabled) { mHost.addTile(HOTSPOT); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index 6857337e5c107..20b501821a97c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -665,7 +665,7 @@ public class PhoneStatusBarPolicy implements Callback, Callbacks, private final HotspotController.Callback mHotspotCallback = new HotspotController.Callback() { @Override - public void onHotspotChanged(boolean enabled) { + public void onHotspotChanged(boolean enabled, int numDevices) { mIconController.setIconVisibility(mSlotHotspot, enabled); } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java index 6457209a407b3..830b50e354906 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotController.java @@ -26,7 +26,9 @@ public interface HotspotController extends CallbackController, Dumpabl void setHotspotEnabled(boolean enabled); boolean isHotspotSupported(); - public interface Callback { - void onHotspotChanged(boolean enabled); + int getNumConnectedDevices(); + + interface Callback { + void onHotspotChanged(boolean enabled, int numDevices); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java index 1ebb986e44885..8792b4f3cc4c1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java @@ -23,31 +23,35 @@ import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.wifi.WifiManager; -import android.os.Handler; import android.os.UserManager; import android.util.Log; +import com.android.systemui.Dependency; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; -public class HotspotControllerImpl implements HotspotController { +public class HotspotControllerImpl implements HotspotController, WifiManager.SoftApCallback { private static final String TAG = "HotspotController"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private final ArrayList mCallbacks = new ArrayList(); - private final Receiver mReceiver = new Receiver(); + private final ArrayList mCallbacks = new ArrayList<>(); + private final WifiStateReceiver mWifiStateReceiver = new WifiStateReceiver(); private final ConnectivityManager mConnectivityManager; + private final WifiManager mWifiManager; private final Context mContext; private int mHotspotState; + private int mNumConnectedDevices; private boolean mWaitingForCallback; public HotspotControllerImpl(Context context) { mContext = context; - mConnectivityManager = (ConnectivityManager) context.getSystemService( - Context.CONNECTIVITY_SERVICE); + mConnectivityManager = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); } @Override @@ -84,7 +88,8 @@ public class HotspotControllerImpl implements HotspotController { if (callback == null || mCallbacks.contains(callback)) return; if (DEBUG) Log.d(TAG, "addCallback " + callback); mCallbacks.add(callback); - mReceiver.setListening(!mCallbacks.isEmpty()); + + updateWifiStateListeners(!mCallbacks.isEmpty()); } } @@ -94,7 +99,26 @@ public class HotspotControllerImpl implements HotspotController { if (DEBUG) Log.d(TAG, "removeCallback " + callback); synchronized (mCallbacks) { mCallbacks.remove(callback); - mReceiver.setListening(!mCallbacks.isEmpty()); + + updateWifiStateListeners(!mCallbacks.isEmpty()); + } + } + + /** + * Updates the wifi state receiver to either start or stop listening to get updates to the + * hotspot status. Additionally starts listening to wifi manager state to track the number of + * connected devices. + * + * @param shouldListen whether we should start listening to various wifi statuses + */ + private void updateWifiStateListeners(boolean shouldListen) { + mWifiStateReceiver.setListening(shouldListen); + if (shouldListen) { + mWifiManager.registerSoftApCallback( + this, + Dependency.get(Dependency.MAIN_HANDLER)); + } else { + mWifiManager.unregisterSoftApCallback(this); } } @@ -116,20 +140,55 @@ public class HotspotControllerImpl implements HotspotController { if (DEBUG) Log.d(TAG, "Starting tethering"); mConnectivityManager.startTethering( ConnectivityManager.TETHERING_WIFI, false, callback); - fireCallback(isHotspotEnabled()); + fireHotspotChangedCallback(isHotspotEnabled()); } else { mConnectivityManager.stopTethering(ConnectivityManager.TETHERING_WIFI); } } - private void fireCallback(boolean isEnabled) { + @Override + public int getNumConnectedDevices() { + return mNumConnectedDevices; + } + + /** + * Sends a hotspot changed callback with the new enabled status. Wraps + * {@link #fireHotspotChangedCallback(boolean, int)} and assumes that the number of devices has + * not changed. + * + * @param enabled whether the hotspot is enabled + */ + private void fireHotspotChangedCallback(boolean enabled) { + fireHotspotChangedCallback(enabled, mNumConnectedDevices); + } + + /** + * Sends a hotspot changed callback with the new enabled status & the number of devices + * connected to the hotspot. Be careful when calling over multiple threads, especially if one of + * them is the main thread (as it can be blocked). + * + * @param enabled whether the hotspot is enabled + * @param numConnectedDevices number of devices connected to the hotspot + */ + private void fireHotspotChangedCallback(boolean enabled, int numConnectedDevices) { synchronized (mCallbacks) { for (Callback callback : mCallbacks) { - callback.onHotspotChanged(isEnabled); + callback.onHotspotChanged(enabled, numConnectedDevices); } } } + @Override + public void onStateChanged(int state, int failureReason) { + // Do nothing - we don't care about changing anything here. + } + + @Override + public void onNumClientsChanged(int numConnectedDevices) { + mNumConnectedDevices = numConnectedDevices; + fireHotspotChangedCallback(isHotspotEnabled(), numConnectedDevices); + } + private final class OnStartTetheringCallback extends ConnectivityManager.OnStartTetheringCallback { @Override @@ -143,12 +202,15 @@ public class HotspotControllerImpl implements HotspotController { public void onTetheringFailed() { if (DEBUG) Log.d(TAG, "onTetheringFailed"); mWaitingForCallback = false; - fireCallback(isHotspotEnabled()); + fireHotspotChangedCallback(isHotspotEnabled()); // TODO: Show error. } } - private final class Receiver extends BroadcastReceiver { + /** + * Class to listen in on wifi state and update the hotspot state + */ + private final class WifiStateReceiver extends BroadcastReceiver { private boolean mRegistered; public void setListening(boolean listening) { @@ -170,8 +232,17 @@ public class HotspotControllerImpl implements HotspotController { int state = intent.getIntExtra( WifiManager.EXTRA_WIFI_AP_STATE, WifiManager.WIFI_AP_STATE_FAILED); if (DEBUG) Log.d(TAG, "onReceive " + state); + + // Update internal hotspot state for tracking before using any enabled/callback methods. mHotspotState = state; - fireCallback(mHotspotState == WifiManager.WIFI_AP_STATE_ENABLED); + + if (!isHotspotEnabled()) { + // Reset num devices if the hotspot is no longer enabled so we don't get ghost + // counters. + mNumConnectedDevices = 0; + } + + fireHotspotChangedCallback(isHotspotEnabled()); } } } diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml index 859dc2f18dc60..f5e079ca9d614 100644 --- a/packages/SystemUI/tests/AndroidManifest.xml +++ b/packages/SystemUI/tests/AndroidManifest.xml @@ -47,6 +47,7 @@ + diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java index 54911473fc1cb..016160aea433e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java @@ -44,4 +44,9 @@ public class FakeHotspotController extends BaseLeakChecker implements public boolean isHotspotSupported() { return false; } + + @Override + public int getNumConnectedDevices() { + return 0; + } }