1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.systemui.statusbar.policy; 17 18 import static com.android.systemui.statusbar.policy.NetworkControllerImpl.TAG; 19 20 import android.annotation.NonNull; 21 import android.content.Context; 22 import android.text.format.DateFormat; 23 import android.util.Log; 24 25 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback; 26 27 import java.io.PrintWriter; 28 import java.util.BitSet; 29 30 31 /** 32 * Common base class for handling signal for both wifi and mobile data. 33 */ 34 public abstract class SignalController<T extends SignalController.State, 35 I extends SignalController.IconGroup> { 36 // Save the previous SignalController.States of all SignalControllers for dumps. 37 static final boolean RECORD_HISTORY = true; 38 // If RECORD_HISTORY how many to save, must be a power of 2. 39 static final int HISTORY_SIZE = 64; 40 41 protected static final boolean DEBUG = NetworkControllerImpl.DEBUG; 42 protected static final boolean CHATTY = NetworkControllerImpl.CHATTY; 43 44 protected final String mTag; 45 protected final T mCurrentState; 46 protected final T mLastState; 47 protected final int mTransportType; 48 protected final Context mContext; 49 // The owner of the SignalController (i.e. NetworkController will maintain the following 50 // lists and call notifyListeners whenever the list has changed to ensure everyone 51 // is aware of current state. 52 protected final NetworkControllerImpl mNetworkController; 53 54 private final CallbackHandler mCallbackHandler; 55 56 // Save the previous HISTORY_SIZE states for logging. 57 private final State[] mHistory; 58 // Where to copy the next state into. 59 private int mHistoryIndex; 60 SignalController(String tag, Context context, int type, CallbackHandler callbackHandler, NetworkControllerImpl networkController)61 public SignalController(String tag, Context context, int type, CallbackHandler callbackHandler, 62 NetworkControllerImpl networkController) { 63 mTag = TAG + "." + tag; 64 mNetworkController = networkController; 65 mTransportType = type; 66 mContext = context; 67 mCallbackHandler = callbackHandler; 68 mCurrentState = cleanState(); 69 mLastState = cleanState(); 70 if (RECORD_HISTORY) { 71 mHistory = new State[HISTORY_SIZE]; 72 for (int i = 0; i < HISTORY_SIZE; i++) { 73 mHistory[i] = cleanState(); 74 } 75 } 76 } 77 getState()78 public T getState() { 79 return mCurrentState; 80 } 81 updateConnectivity(BitSet connectedTransports, BitSet validatedTransports)82 public void updateConnectivity(BitSet connectedTransports, BitSet validatedTransports) { 83 mCurrentState.inetCondition = validatedTransports.get(mTransportType) ? 1 : 0; 84 notifyListenersIfNecessary(); 85 } 86 87 /** 88 * Used at the end of demo mode to clear out any ugly state that it has created. 89 * Since we haven't had any callbacks, then isDirty will not have been triggered, 90 * so we can just take the last good state directly from there. 91 * 92 * Used for demo mode. 93 */ resetLastState()94 public void resetLastState() { 95 mCurrentState.copyFrom(mLastState); 96 } 97 98 /** 99 * Determines if the state of this signal controller has changed and 100 * needs to trigger callbacks related to it. 101 */ isDirty()102 public boolean isDirty() { 103 if (!mLastState.equals(mCurrentState)) { 104 if (DEBUG) { 105 Log.d(mTag, "Change in state from: " + mLastState + "\n" 106 + "\tto: " + mCurrentState); 107 } 108 return true; 109 } 110 return false; 111 } 112 saveLastState()113 public void saveLastState() { 114 if (RECORD_HISTORY) { 115 recordLastState(); 116 } 117 // Updates the current time. 118 mCurrentState.time = System.currentTimeMillis(); 119 mLastState.copyFrom(mCurrentState); 120 } 121 122 /** 123 * Gets the signal icon for QS based on current state of connected, enabled, and level. 124 */ getQsCurrentIconId()125 public int getQsCurrentIconId() { 126 if (mCurrentState.connected) { 127 return getIcons().mQsIcons[mCurrentState.inetCondition][mCurrentState.level]; 128 } else if (mCurrentState.enabled) { 129 return getIcons().mQsDiscState; 130 } else { 131 return getIcons().mQsNullState; 132 } 133 } 134 135 /** 136 * Gets the signal icon for SB based on current state of connected, enabled, and level. 137 */ getCurrentIconId()138 public int getCurrentIconId() { 139 if (mCurrentState.connected) { 140 return getIcons().mSbIcons[mCurrentState.inetCondition][mCurrentState.level]; 141 } else if (mCurrentState.enabled) { 142 return getIcons().mSbDiscState; 143 } else { 144 return getIcons().mSbNullState; 145 } 146 } 147 148 /** 149 * Gets the content description id for the signal based on current state of connected and 150 * level. 151 */ getContentDescription()152 public int getContentDescription() { 153 if (mCurrentState.connected) { 154 return getIcons().mContentDesc[mCurrentState.level]; 155 } else { 156 return getIcons().mDiscContentDesc; 157 } 158 } 159 notifyListenersIfNecessary()160 public void notifyListenersIfNecessary() { 161 if (isDirty()) { 162 saveLastState(); 163 notifyListeners(); 164 } 165 } 166 167 /** 168 * Returns the resource if resId is not 0, and an empty string otherwise. 169 */ getStringIfExists(int resId)170 @NonNull CharSequence getStringIfExists(int resId) { 171 return resId != 0 ? mContext.getText(resId) : ""; 172 } 173 getIcons()174 protected I getIcons() { 175 return (I) mCurrentState.iconGroup; 176 } 177 178 /** 179 * Saves the last state of any changes, so we can log the current 180 * and last value of any state data. 181 */ recordLastState()182 protected void recordLastState() { 183 mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)].copyFrom(mLastState); 184 } 185 dump(PrintWriter pw)186 public void dump(PrintWriter pw) { 187 pw.println(" - " + mTag + " -----"); 188 pw.println(" Current State: " + mCurrentState); 189 if (RECORD_HISTORY) { 190 // Count up the states that actually contain time stamps, and only display those. 191 int size = 0; 192 for (int i = 0; i < HISTORY_SIZE; i++) { 193 if (mHistory[i].time != 0) size++; 194 } 195 // Print out the previous states in ordered number. 196 for (int i = mHistoryIndex + HISTORY_SIZE - 1; 197 i >= mHistoryIndex + HISTORY_SIZE - size; i--) { 198 pw.println(" Previous State(" + (mHistoryIndex + HISTORY_SIZE - i) + "): " 199 + mHistory[i & (HISTORY_SIZE - 1)]); 200 } 201 } 202 } 203 notifyListeners()204 public final void notifyListeners() { 205 notifyListeners(mCallbackHandler); 206 } 207 208 /** 209 * Trigger callbacks based on current state. The callbacks should be completely 210 * based on current state, and only need to be called in the scenario where 211 * mCurrentState != mLastState. 212 */ notifyListeners(SignalCallback callback)213 public abstract void notifyListeners(SignalCallback callback); 214 215 /** 216 * Generate a blank T. 217 */ cleanState()218 protected abstract T cleanState(); 219 220 /* 221 * Holds icons for a given state. Arrays are generally indexed as inet 222 * state (full connectivity or not) first, and second dimension as 223 * signal strength. 224 */ 225 static class IconGroup { 226 final int[][] mSbIcons; 227 final int[][] mQsIcons; 228 final int[] mContentDesc; 229 final int mSbNullState; 230 final int mQsNullState; 231 final int mSbDiscState; 232 final int mQsDiscState; 233 final int mDiscContentDesc; 234 // For logging. 235 final String mName; 236 IconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc, int sbNullState, int qsNullState, int sbDiscState, int qsDiscState, int discContentDesc)237 public IconGroup(String name, int[][] sbIcons, int[][] qsIcons, int[] contentDesc, 238 int sbNullState, int qsNullState, int sbDiscState, int qsDiscState, 239 int discContentDesc) { 240 mName = name; 241 mSbIcons = sbIcons; 242 mQsIcons = qsIcons; 243 mContentDesc = contentDesc; 244 mSbNullState = sbNullState; 245 mQsNullState = qsNullState; 246 mSbDiscState = sbDiscState; 247 mQsDiscState = qsDiscState; 248 mDiscContentDesc = discContentDesc; 249 } 250 251 @Override toString()252 public String toString() { 253 return "IconGroup(" + mName + ")"; 254 } 255 } 256 257 static class State { 258 boolean connected; 259 boolean enabled; 260 boolean activityIn; 261 boolean activityOut; 262 int level; 263 IconGroup iconGroup; 264 int inetCondition; 265 int rssi; // Only for logging. 266 267 // Not used for comparison, just used for logging. 268 long time; 269 copyFrom(State state)270 public void copyFrom(State state) { 271 connected = state.connected; 272 enabled = state.enabled; 273 level = state.level; 274 iconGroup = state.iconGroup; 275 inetCondition = state.inetCondition; 276 activityIn = state.activityIn; 277 activityOut = state.activityOut; 278 rssi = state.rssi; 279 time = state.time; 280 } 281 282 @Override toString()283 public String toString() { 284 if (time != 0) { 285 StringBuilder builder = new StringBuilder(); 286 toString(builder); 287 return builder.toString(); 288 } else { 289 return "Empty " + getClass().getSimpleName(); 290 } 291 } 292 toString(StringBuilder builder)293 protected void toString(StringBuilder builder) { 294 builder.append("connected=").append(connected).append(',') 295 .append("enabled=").append(enabled).append(',') 296 .append("level=").append(level).append(',') 297 .append("inetCondition=").append(inetCondition).append(',') 298 .append("iconGroup=").append(iconGroup).append(',') 299 .append("activityIn=").append(activityIn).append(',') 300 .append("activityOut=").append(activityOut).append(',') 301 .append("rssi=").append(rssi).append(',') 302 .append("lastModified=").append(DateFormat.format("MM-dd HH:mm:ss", time)); 303 } 304 305 @Override equals(Object o)306 public boolean equals(Object o) { 307 if (!o.getClass().equals(getClass())) { 308 return false; 309 } 310 State other = (State) o; 311 return other.connected == connected 312 && other.enabled == enabled 313 && other.level == level 314 && other.inetCondition == inetCondition 315 && other.iconGroup == iconGroup 316 && other.activityIn == activityIn 317 && other.activityOut == activityOut 318 && other.rssi == rssi; 319 } 320 } 321 } 322