/* * 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 android.content.Context; import android.text.format.DateFormat; import android.util.Log; import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; import java.io.PrintWriter; import java.util.BitSet; import static com.android.systemui.statusbar.policy.NetworkControllerImpl.TAG; /** * Common base class for handling signal for both wifi and mobile data. */ public abstract class SignalController { // 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; private 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)]); } } } public final void notifyListeners() { notifyListeners(mCallbackHandler); } /** * 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(SignalCallback callback); /** * 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; } } }