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