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.connectivity;
17 
18 import static com.android.systemui.statusbar.connectivity.NetworkControllerImpl.TAG;
19 
20 import android.annotation.NonNull;
21 import android.content.Context;
22 import android.util.Log;
23 
24 import com.android.settingslib.SignalIcon.IconGroup;
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  * @param <T> State of the SysUI controller.
34  * @param <I> Icon groups of the SysUI controller for a given State.
35  */
36 public abstract class SignalController<T extends ConnectivityState, I extends IconGroup> {
37     // Save the previous SignalController.States of all SignalControllers for dumps.
38     static final boolean RECORD_HISTORY = true;
39     // If RECORD_HISTORY how many to save, must be a power of 2.
40     static final int HISTORY_SIZE = 64;
41 
42     protected static final boolean DEBUG = NetworkControllerImpl.DEBUG;
43     protected static final boolean CHATTY = NetworkControllerImpl.CHATTY;
44 
45     protected final String mTag;
46     protected final T mCurrentState;
47     protected final T mLastState;
48     protected final int mTransportType;
49     protected final Context mContext;
50     // The owner of the SignalController (i.e. NetworkController will maintain the following
51     // lists and call notifyListeners whenever the list has changed to ensure everyone
52     // is aware of current state.
53     protected final NetworkControllerImpl mNetworkController;
54 
55     private final CallbackHandler mCallbackHandler;
56 
57     // Save the previous HISTORY_SIZE states for logging.
58     private final ConnectivityState[] mHistory;
59     // Where to copy the next state into.
60     private int mHistoryIndex;
61 
SignalController(String tag, Context context, int type, CallbackHandler callbackHandler, NetworkControllerImpl networkController)62     public SignalController(String tag, Context context, int type, CallbackHandler callbackHandler,
63             NetworkControllerImpl networkController) {
64         mTag = TAG + "." + tag;
65         mNetworkController = networkController;
66         mTransportType = type;
67         mContext = context;
68         mCallbackHandler = callbackHandler;
69         mCurrentState = cleanState();
70         mLastState = cleanState();
71         if (RECORD_HISTORY) {
72             mHistory = new ConnectivityState[HISTORY_SIZE];
73             for (int i = 0; i < HISTORY_SIZE; i++) {
74                 mHistory[i] = cleanState();
75             }
76         }
77     }
78 
getState()79     public T getState() {
80         return mCurrentState;
81     }
82 
updateConnectivity(BitSet connectedTransports, BitSet validatedTransports)83     void updateConnectivity(BitSet connectedTransports, BitSet validatedTransports) {
84         mCurrentState.inetCondition = validatedTransports.get(mTransportType) ? 1 : 0;
85         notifyListenersIfNecessary();
86     }
87 
88     /**
89      * Used at the end of demo mode to clear out any ugly state that it has created.
90      * Since we haven't had any callbacks, then isDirty will not have been triggered,
91      * so we can just take the last good state directly from there.
92      *
93      * Used for demo mode.
94      */
resetLastState()95     public void resetLastState() {
96         mCurrentState.copyFrom(mLastState);
97     }
98 
99     /**
100      * Determines if the state of this signal controller has changed and
101      * needs to trigger callbacks related to it.
102      */
isDirty()103     public boolean isDirty() {
104         if (!mLastState.equals(mCurrentState)) {
105             if (DEBUG) {
106                 Log.d(mTag, "Change in state from: " + mLastState + "\n"
107                         + "\tto: " + mCurrentState);
108             }
109             return true;
110         }
111         return false;
112     }
113 
saveLastState()114     void saveLastState() {
115         if (RECORD_HISTORY) {
116             recordLastState();
117         }
118         // Updates the current time.
119         mCurrentState.time = System.currentTimeMillis();
120         mLastState.copyFrom(mCurrentState);
121     }
122 
123     /**
124      * Gets the signal icon for QS based on current state of connected, enabled, and level.
125      */
getQsCurrentIconId()126     public int getQsCurrentIconId() {
127         if (mCurrentState.connected) {
128             return getIcons().qsIcons[mCurrentState.inetCondition][mCurrentState.level];
129         } else if (mCurrentState.enabled) {
130             return getIcons().qsDiscState;
131         } else {
132             return getIcons().qsNullState;
133         }
134     }
135 
136     /**
137      * Gets the signal icon for SB based on current state of connected, enabled, and level.
138      */
getCurrentIconId()139     public int getCurrentIconId() {
140         if (mCurrentState.connected) {
141             return getIcons().sbIcons[mCurrentState.inetCondition][mCurrentState.level];
142         } else if (mCurrentState.enabled) {
143             return getIcons().sbDiscState;
144         } else {
145             return getIcons().sbNullState;
146         }
147     }
148 
149     /**
150      * Gets the content description id for the signal based on current state of connected and
151      * level.
152      */
getContentDescription()153     public int getContentDescription() {
154         if (mCurrentState.connected) {
155             return getIcons().contentDesc[mCurrentState.level];
156         } else {
157             return getIcons().discContentDesc;
158         }
159     }
160 
notifyListenersIfNecessary()161     void notifyListenersIfNecessary() {
162         if (isDirty()) {
163             saveLastState();
164             notifyListeners();
165         }
166     }
167 
notifyCallStateChange(IconState statusIcon, int subId)168     protected final void notifyCallStateChange(IconState statusIcon, int subId) {
169         mCallbackHandler.setCallIndicator(statusIcon, subId);
170     }
171 
172     /**
173      * Returns the resource if resId is not 0, and an empty string otherwise.
174      */
getTextIfExists(int resId)175     @NonNull CharSequence getTextIfExists(int resId) {
176         return resId != 0 ? mContext.getText(resId) : "";
177     }
178 
getIcons()179     protected I getIcons() {
180         return (I) mCurrentState.iconGroup;
181     }
182 
183     /**
184      * Saves the last state of any changes, so we can log the current
185      * and last value of any state data.
186      */
recordLastState()187     protected void recordLastState() {
188         mHistory[mHistoryIndex].copyFrom(mLastState);
189         mHistoryIndex = (mHistoryIndex + 1) % HISTORY_SIZE;
190     }
191 
dump(PrintWriter pw)192     void dump(PrintWriter pw) {
193         pw.println("  - " + mTag + " -----");
194         pw.println("  Current State: " + mCurrentState);
195         if (RECORD_HISTORY) {
196             // Count up the states that actually contain time stamps, and only display those.
197             int size = 0;
198             for (int i = 0; i < HISTORY_SIZE; i++) {
199                 if (mHistory[i].time != 0) size++;
200             }
201             // Print out the previous states in ordered number.
202             for (int i = mHistoryIndex + HISTORY_SIZE - 1;
203                     i >= mHistoryIndex + HISTORY_SIZE - size; i--) {
204                 pw.println("  Previous State(" + (mHistoryIndex + HISTORY_SIZE - i) + "): "
205                         + mHistory[i & (HISTORY_SIZE - 1)]);
206             }
207         }
208     }
209 
notifyListeners()210     final void notifyListeners() {
211         notifyListeners(mCallbackHandler);
212     }
213 
214     /**
215      * Trigger callbacks based on current state.  The callbacks should be completely
216      * based on current state, and only need to be called in the scenario where
217      * mCurrentState != mLastState.
218      */
notifyListeners(SignalCallback callback)219     abstract void notifyListeners(SignalCallback callback);
220 
221     /**
222      * Generate a blank T.
223      */
cleanState()224     protected abstract T cleanState();
225 }
226