/*
* Copyright (C) 2015 The Android Open Source 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.policy.NetworkControllerImpl.TAG;
import android.content.Context;
import android.text.format.DateFormat;
import android.util.Log;
import java.io.PrintWriter;
import java.util.BitSet;
/**
* Common base class for handling signal for both wifi and mobile data.
*/
public abstract class SignalController<T extends SignalController.State,
I extends SignalController.IconGroup> {
// Save the previous SignalController.States of all SignalControllers for dumps.
static final boolean RECORD_HISTORY = true;
// If RECORD_HISTORY how many to save, must be a power of 2.
static final int HISTORY_SIZE = 64;
protected static final boolean DEBUG = NetworkControllerImpl.DEBUG;
protected static final boolean CHATTY = NetworkControllerImpl.CHATTY;
protected final String mTag;
protected final T mCurrentState;
protected final T mLastState;
protected final int mTransportType;
protected final Context mContext;
// The owner of the SignalController (i.e. NetworkController will maintain the following
// lists and call notifyListeners whenever the list has changed to ensure everyone
// is aware of current state.
protected final NetworkControllerImpl mNetworkController;
protected final CallbackHandler mCallbackHandler;
// Save the previous HISTORY_SIZE states for logging.
private final State[] mHistory;
// Where to copy the next state into.
private int mHistoryIndex;
public SignalController(String tag, Context context, int type, CallbackHandler callbackHandler,
NetworkControllerImpl networkController) {
mTag = TAG + "." + tag;
mNetworkController = networkController;
mTransportType = type;
mContext = context;
mCallbackHandler = callbackHandler;
mCurrentState = cleanState();
mLastState = cleanState();
if (RECORD_HISTORY) {
mHistory = new State[HISTORY_SIZE];
for (int i = 0; i < HISTORY_SIZE; i++) {
mHistory[i] = cleanState();
}
}
}
public T getState() {
return mCurrentState;
}
public void updateConnectivity(BitSet connectedTransports, BitSet validatedTransports) {
mCurrentState.inetCondition = validatedTransports.get(mTransportType) ? 1 : 0;
notifyListenersIfNecessary();
}
/**
* Used at the end of demo mode to clear out any ugly state that it has created.
* Since we haven't had any callbacks, then isDirty will not have been triggered,
* so we can just take the last good state directly from there.
*
* Used for demo mode.
*/
public void resetLastState() {
mCurrentState.copyFrom(mLastState);
}
/**
* Determines if the state of this signal controller has changed and
* needs to trigger callbacks related to it.
*/
public boolean isDirty() {
if (!mLastState.equals(mCurrentState)) {
if (DEBUG) {
Log.d(mTag, "Change in state from: " + mLastState + "\n"
+ "\tto: " + mCurrentState);
}
return true;
}
return false;
}
public void saveLastState() {
if (RECORD_HISTORY) {
recordLastState();
}
// Updates the current time.
mCurrentState.time = System.currentTimeMillis();
mLastState.copyFrom(mCurrentState);
}
/**
* Gets the signal icon for QS based on current state of connected, enabled, and level.
*/
public int getQsCurrentIconId() {
if (mCurrentState.connected) {
return getIcons().mQsIcons[mCurrentState.inetCondition][mCurrentState.level];
} else if (mCurrentState.enabled) {
return getIcons().mQsDiscState;
} else {
return getIcons().mQsNullState;
}
}
/**
* Gets the signal icon for SB based on current state of connected, enabled, and level.
*/
public int getCurrentIconId() {
if (mCurrentState.connected) {
return getIcons().mSbIcons[mCurrentState.inetCondition][mCurrentState.level];
} else if (mCurrentState.enabled) {
return getIcons().mSbDiscState;
} else {
return getIcons().mSbNullState;
}
}
/**
* Gets the content description id for the signal based on current state of connected and
* level.
*/
public int getContentDescription() {
if (mCurrentState.connected) {
return getIcons().mContentDesc[mCurrentState.level];
} else {
return getIcons().mDiscContentDesc;
}
}
public void notifyListenersIfNecessary() {
if (isDirty()) {
saveLastState();
notifyListeners();
}
}
/**
* Returns the resource if resId is not 0, and an empty string otherwise.
*/
protected String getStringIfExists(int resId) {
return resId != 0 ? mContext.getString(resId) : "";
}
protected I getIcons() {
return (I) mCurrentState.iconGroup;
}
/**
* Saves the last state of any changes, so we can log the current
* and last value of any state data.
*/
protected void recordLastState() {
mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)].copyFrom(mLastState);
}
public void dump(PrintWriter pw) {
pw.println(" - " + mTag + " -----");
pw.println(" Current State: " + mCurrentState);
if (RECORD_HISTORY) {
// Count up the states that actually contain time stamps, and only display those.
int size = 0;
for (int i = 0; i < HISTORY_SIZE; i++) {
if (mHistory[i].time != 0) size++;
}
// Print out the previous states in ordered number.
for (int i = mHistoryIndex + HISTORY_SIZE - 1;
i >= mHistoryIndex + HISTORY_SIZE - size; i--) {
pw.println(" Previous State(" + (mHistoryIndex + HISTORY_SIZE - i) + "): "
+ mHistory[i & (HISTORY_SIZE - 1)]);
}
}
}
/**
* Trigger callbacks based on current state. The callbacks should be completely
* based on current state, and only need to be called in the scenario where
* mCurrentState != mLastState.
*/
public abstract void notifyListeners();
/**
* Generate a blank T.
*/
protected abstract T cleanState();
/*
* Holds icons for a given state. Arrays are generally indexed as inet
* state (full connectivity or not) first, and second dimension as
* signal strength.
*/
static class IconGroup {
final int[][] mSbIcons;
final int[][] mQsIcons;
final int[] mContentDesc;
final int mSbNullState;
final int mQsNullState;
final int mSbDiscState;
final int mQsDiscState;
final int mDiscContentDesc;
// For logging.
final String mName;
public IconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc,
int sbNullState, int qsNullState, int sbDiscState, int qsDiscState,
int discContentDesc) {
mName = name;
mSbIcons = sbIcons;
mQsIcons = qsIcons;
mContentDesc = contentDesc;
mSbNullState = sbNullState;
mQsNullState = qsNullState;
mSbDiscState = sbDiscState;
mQsDiscState = qsDiscState;
mDiscContentDesc = discContentDesc;
}
@Override
public String toString() {
return "IconGroup(" + mName + ")";
}
}
static class State {
boolean connected;
boolean enabled;
boolean activityIn;
boolean activityOut;
int level;
IconGroup iconGroup;
int inetCondition;
int rssi; // Only for logging.
// Not used for comparison, just used for logging.
long time;
public void copyFrom(State state) {
connected = state.connected;
enabled = state.enabled;
level = state.level;
iconGroup = state.iconGroup;
inetCondition = state.inetCondition;
activityIn = state.activityIn;
activityOut = state.activityOut;
rssi = state.rssi;
time = state.time;
}
@Override
public String toString() {
if (time != 0) {
StringBuilder builder = new StringBuilder();
toString(builder);
return builder.toString();
} else {
return "Empty " + getClass().getSimpleName();
}
}
protected void toString(StringBuilder builder) {
builder.append("connected=").append(connected).append(',')
.append("enabled=").append(enabled).append(',')
.append("level=").append(level).append(',')
.append("inetCondition=").append(inetCondition).append(',')
.append("iconGroup=").append(iconGroup).append(',')
.append("activityIn=").append(activityIn).append(',')
.append("activityOut=").append(activityOut).append(',')
.append("rssi=").append(rssi).append(',')
.append("lastModified=").append(DateFormat.format("MM-dd hh:mm:ss", time));
}
@Override
public boolean equals(Object o) {
if (!o.getClass().equals(getClass())) {
return false;
}
State other = (State) o;
return other.connected == connected
&& other.enabled == enabled
&& other.level == level
&& other.inetCondition == inetCondition
&& other.iconGroup == iconGroup
&& other.activityIn == activityIn
&& other.activityOut == activityOut
&& other.rssi == rssi;
}
}
}