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