[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
This commit is contained in:
Rohan Shah
2018-01-22 15:16:09 -08:00
parent a14cde6eb7
commit e4071127a6
8 changed files with 150 additions and 24 deletions

View File

@@ -753,6 +753,11 @@
<string name="quick_settings_tethering_label">Tethering</string>
<!-- QuickSettings: Hotspot. [CHAR LIMIT=NONE] -->
<string name="quick_settings_hotspot_label">Hotspot</string>
<!-- QuickSettings: Hotspot: Secondary label for how many devices are connected to the hotspot [CHAR LIMIT=NONE] -->
<plurals name="quick_settings_hotspot_num_devices">
<item quantity="one">%d device</item>
<item quantity="other">%d devices</item>
</plurals>
<!-- QuickSettings: Notifications [CHAR LIMIT=NONE] -->
<string name="quick_settings_notifications_label">Notifications</string>
<!-- QuickSettings: Flashlight [CHAR LIMIT=NONE] -->

View File

@@ -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<AirplaneBooleanState> {
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<AirplaneBooleanState> {
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<AirplaneBooleanState> {
: 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<AirplaneBooleanState> {
}
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();
}
}
}

View File

@@ -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);

View File

@@ -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);
}
};

View File

@@ -26,7 +26,9 @@ public interface HotspotController extends CallbackController<Callback>, Dumpabl
void setHotspotEnabled(boolean enabled);
boolean isHotspotSupported();
public interface Callback {
void onHotspotChanged(boolean enabled);
int getNumConnectedDevices();
interface Callback {
void onHotspotChanged(boolean enabled, int numDevices);
}
}

View File

@@ -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<Callback> mCallbacks = new ArrayList<Callback>();
private final Receiver mReceiver = new Receiver();
private final ArrayList<Callback> 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());
}
}
}

View File

@@ -47,6 +47,7 @@
<uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
<uses-permission android:name="android.permission.REAL_GET_TASKS" />
<uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" />
<uses-permission android:name="android.permission.NETWORK_SETTINGS" />
<application>
<uses-library android:name="android.test.runner" />

View File

@@ -44,4 +44,9 @@ public class FakeHotspotController extends BaseLeakChecker<Callback> implements
public boolean isHotspotSupported() {
return false;
}
@Override
public int getNumConnectedDevices() {
return 0;
}
}