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