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