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.connectivity; 17 18 import static com.android.systemui.statusbar.connectivity.NetworkControllerImpl.TAG; 19 20 import android.annotation.NonNull; 21 import android.content.Context; 22 import android.util.Log; 23 24 import com.android.settingslib.SignalIcon.IconGroup; 25 import com.android.systemui.dump.DumpsysTableLogger; 26 27 import java.io.PrintWriter; 28 import java.util.ArrayList; 29 import java.util.BitSet; 30 import java.util.List; 31 32 33 /** 34 * Common base class for handling signal for both wifi and mobile data. 35 * 36 * @param <T> State of the SysUI controller. 37 * @param <I> Icon groups of the SysUI controller for a given State. 38 */ 39 public abstract class SignalController<T extends ConnectivityState, I extends IconGroup> { 40 // Save the previous SignalController.States of all SignalControllers for dumps. 41 static final boolean RECORD_HISTORY = true; 42 // If RECORD_HISTORY how many to save, must be a power of 2. 43 static final int HISTORY_SIZE = 64; 44 45 protected static final boolean DEBUG = NetworkControllerImpl.DEBUG; 46 protected static final boolean CHATTY = NetworkControllerImpl.CHATTY; 47 48 protected final String mTag; 49 protected final T mCurrentState; 50 protected final T mLastState; 51 protected final int mTransportType; 52 protected final Context mContext; 53 // The owner of the SignalController (i.e. NetworkController) will maintain the following 54 // lists and call notifyListeners whenever the list has changed to ensure everyone 55 // is aware of current state. 56 protected final NetworkControllerImpl mNetworkController; 57 58 private final CallbackHandler mCallbackHandler; 59 60 // Save the previous HISTORY_SIZE states for logging. 61 private final ConnectivityState[] mHistory; 62 // Where to copy the next state into. 63 private int mHistoryIndex; 64 SignalController(String tag, Context context, int type, CallbackHandler callbackHandler, NetworkControllerImpl networkController)65 public SignalController(String tag, Context context, int type, CallbackHandler callbackHandler, 66 NetworkControllerImpl networkController) { 67 mTag = TAG + "." + tag; 68 mNetworkController = networkController; 69 mTransportType = type; 70 mContext = context; 71 mCallbackHandler = callbackHandler; 72 mCurrentState = cleanState(); 73 mLastState = cleanState(); 74 if (RECORD_HISTORY) { 75 mHistory = new ConnectivityState[HISTORY_SIZE]; 76 for (int i = 0; i < HISTORY_SIZE; i++) { 77 mHistory[i] = cleanState(); 78 } 79 } 80 } 81 getState()82 public T getState() { 83 return mCurrentState; 84 } 85 updateConnectivity(BitSet connectedTransports, BitSet validatedTransports)86 void updateConnectivity(BitSet connectedTransports, BitSet validatedTransports) { 87 mCurrentState.inetCondition = validatedTransports.get(mTransportType) ? 1 : 0; 88 notifyListenersIfNecessary(); 89 } 90 91 /** 92 * Used at the end of demo mode to clear out any ugly state that it has created. 93 * Since we haven't had any callbacks, then isDirty will not have been triggered, 94 * so we can just take the last good state directly from there. 95 * 96 * Used for demo mode. 97 */ resetLastState()98 public void resetLastState() { 99 mCurrentState.copyFrom(mLastState); 100 } 101 102 /** 103 * Determines if the state of this signal controller has changed and 104 * needs to trigger callbacks related to it. 105 */ isDirty()106 boolean isDirty() { 107 if (!mLastState.equals(mCurrentState)) { 108 if (DEBUG) { 109 Log.d(mTag, "Change in state from: " + mLastState + "\n" 110 + "\tto: " + mCurrentState); 111 } 112 return true; 113 } 114 return false; 115 } 116 saveLastState()117 void saveLastState() { 118 if (RECORD_HISTORY) { 119 recordLastState(); 120 } 121 // Updates the current time. 122 mCurrentState.time = System.currentTimeMillis(); 123 mLastState.copyFrom(mCurrentState); 124 } 125 126 /** 127 * Gets the signal icon for QS based on current state of connected, enabled, and level. 128 */ getQsCurrentIconId()129 public int getQsCurrentIconId() { 130 if (mCurrentState.connected) { 131 return getIcons().qsIcons[mCurrentState.inetCondition][mCurrentState.level]; 132 } else if (mCurrentState.enabled) { 133 return getIcons().qsDiscState; 134 } else { 135 return getIcons().qsNullState; 136 } 137 } 138 139 /** 140 * Gets the signal icon for SB based on current state of connected, enabled, and level. 141 */ getCurrentIconId()142 public int getCurrentIconId() { 143 if (mCurrentState.connected) { 144 return getIcons().sbIcons[mCurrentState.inetCondition][mCurrentState.level]; 145 } else if (mCurrentState.enabled) { 146 return getIcons().sbDiscState; 147 } else { 148 return getIcons().sbNullState; 149 } 150 } 151 152 /** 153 * Gets the content description id for the signal based on current state of connected and 154 * level. 155 */ getContentDescription()156 public int getContentDescription() { 157 if (mCurrentState.connected) { 158 return getIcons().contentDesc[mCurrentState.level]; 159 } else { 160 return getIcons().discContentDesc; 161 } 162 } 163 notifyListenersIfNecessary()164 void notifyListenersIfNecessary() { 165 if (isDirty()) { 166 saveLastState(); 167 notifyListeners(); 168 } 169 } 170 notifyCallStateChange(IconState statusIcon, int subId)171 protected final void notifyCallStateChange(IconState statusIcon, int subId) { 172 mCallbackHandler.setCallIndicator(statusIcon, subId); 173 } 174 175 /** 176 * Returns the resource if resId is not 0, and an empty string otherwise. 177 */ getTextIfExists(int resId)178 @NonNull CharSequence getTextIfExists(int resId) { 179 return resId != 0 ? mContext.getText(resId) : ""; 180 } 181 getIcons()182 protected I getIcons() { 183 return (I) mCurrentState.iconGroup; 184 } 185 186 /** 187 * Saves the last state of any changes, so we can log the current 188 * and last value of any state data. 189 */ recordLastState()190 protected void recordLastState() { 191 mHistory[mHistoryIndex].copyFrom(mLastState); 192 mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE; 193 } 194 dump(PrintWriter pw)195 void dump(PrintWriter pw) { 196 pw.println(" - " + mTag + " -----"); 197 pw.println(" Current State: " + mCurrentState); 198 if (RECORD_HISTORY) { 199 List<ConnectivityState> history = getOrderedHistoryExcludingCurrentState(); 200 for (int i = 0; i < history.size(); i++) { 201 pw.println(" Previous State(" + (i + 1) + "): " + mHistory[i]); 202 } 203 } 204 } 205 206 /** 207 * mHistory is a ring, so use this method to get the time-ordered (from youngest to oldest) 208 * list of historical states. Filters out any state whose `time` is `0`. 209 * 210 * For ease of compatibility, this list returns JUST the historical states, not the current 211 * state which has yet to be copied into the history 212 * 213 * @see #getOrderedHistory() 214 * @return historical states, ordered from newest to oldest 215 */ getOrderedHistoryExcludingCurrentState()216 List<ConnectivityState> getOrderedHistoryExcludingCurrentState() { 217 ArrayList<ConnectivityState> history = new ArrayList<>(); 218 219 // Count up the states that actually contain time stamps, and only display those. 220 int size = 0; 221 for (int i = 0; i < HISTORY_SIZE; i++) { 222 if (mHistory[i].time != 0) size++; 223 } 224 // Print out the previous states in ordered number. 225 for (int i = mHistoryIndex + HISTORY_SIZE - 1; 226 i >= mHistoryIndex + HISTORY_SIZE - size; i--) { 227 history.add(mHistory[i & (HISTORY_SIZE - 1)]); 228 } 229 230 return history; 231 } 232 233 /** 234 * Get the ordered history states, including the current yet-to-be-copied state. Useful for 235 * logging 236 * 237 * @see #getOrderedHistoryExcludingCurrentState() 238 * @return [currentState, historicalState...] array 239 */ getOrderedHistory()240 List<ConnectivityState> getOrderedHistory() { 241 ArrayList<ConnectivityState> history = new ArrayList<>(); 242 // Start with the current state 243 history.add(mCurrentState); 244 history.addAll(getOrderedHistoryExcludingCurrentState()); 245 return history; 246 } 247 dumpTableData(PrintWriter pw)248 void dumpTableData(PrintWriter pw) { 249 List<List<String>> tableData = new ArrayList<List<String>>(); 250 List<ConnectivityState> history = getOrderedHistory(); 251 for (int i = 0; i < history.size(); i++) { 252 tableData.add(history.get(i).tableData()); 253 } 254 255 DumpsysTableLogger logger = 256 new DumpsysTableLogger(mTag, mCurrentState.tableColumns(), tableData); 257 258 logger.printTableData(pw); 259 } 260 notifyListeners()261 final void notifyListeners() { 262 notifyListeners(mCallbackHandler); 263 } 264 265 /** 266 * Trigger callbacks based on current state. The callbacks should be completely 267 * based on current state, and only need to be called in the scenario where 268 * mCurrentState != mLastState. 269 */ notifyListeners(SignalCallback callback)270 abstract void notifyListeners(SignalCallback callback); 271 272 /** 273 * Generate a blank T. 274 */ cleanState()275 protected abstract T cleanState(); 276 } 277