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