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