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