SignalController.java revision 2fdbe128209f02725c645f29bff941efe865da56
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 */
16package com.android.systemui.statusbar.policy;
17
18import static com.android.systemui.statusbar.policy.NetworkControllerImpl.TAG;
19
20import android.content.Context;
21import android.text.format.DateFormat;
22import android.util.Log;
23
24import java.io.PrintWriter;
25import java.util.BitSet;
26
27
28/**
29 * Common base class for handling signal for both wifi and mobile data.
30 */
31public 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
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
75    public T getState() {
76        return mCurrentState;
77    }
78
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     */
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     */
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
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     */
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     */
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     */
149    public int getContentDescription() {
150        if (mCurrentState.connected) {
151            return getIcons().mContentDesc[mCurrentState.level];
152        } else {
153            return getIcons().mDiscContentDesc;
154        }
155    }
156
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     */
167    protected String getStringIfExists(int resId) {
168        return resId != 0 ? mContext.getString(resId) : "";
169    }
170
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     */
179    protected void recordLastState() {
180        mHistory[mHistoryIndex++ & (HISTORY_SIZE - 1)].copyFrom(mLastState);
181    }
182
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     */
206    public abstract void notifyListeners();
207
208    /**
209     * Generate a blank T.
210     */
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
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
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
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
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
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
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