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