1/*
2 * Copyright (C) 2011 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
17package com.android.systemui.statusbar;
18
19import android.content.Context;
20import android.telephony.SubscriptionInfo;
21import android.util.AttributeSet;
22import android.util.Log;
23import android.view.LayoutInflater;
24import android.view.View;
25import android.view.ViewGroup;
26import android.view.accessibility.AccessibilityEvent;
27import android.widget.ImageView;
28import android.widget.LinearLayout;
29
30import com.android.systemui.R;
31import com.android.systemui.statusbar.policy.NetworkControllerImpl;
32import com.android.systemui.statusbar.policy.SecurityController;
33
34import java.util.ArrayList;
35import java.util.List;
36
37// Intimately tied to the design of res/layout/signal_cluster_view.xml
38public class SignalClusterView
39        extends LinearLayout
40        implements NetworkControllerImpl.SignalCluster,
41        SecurityController.SecurityControllerCallback {
42
43    static final String TAG = "SignalClusterView";
44    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
45
46    NetworkControllerImpl mNC;
47    SecurityController mSC;
48
49    private boolean mNoSimsVisible = false;
50    private boolean mVpnVisible = false;
51    private boolean mWifiVisible = false;
52    private int mWifiStrengthId = 0;
53    private boolean mIsAirplaneMode = false;
54    private int mAirplaneIconId = 0;
55    private int mAirplaneContentDescription;
56    private String mWifiDescription;
57    private ArrayList<PhoneState> mPhoneStates = new ArrayList<PhoneState>();
58
59    ViewGroup mWifiGroup;
60    ImageView mVpn, mWifi, mAirplane, mNoSims;
61    View mWifiAirplaneSpacer;
62    View mWifiSignalSpacer;
63    LinearLayout mMobileSignalGroup;
64
65    private int mWideTypeIconStartPadding;
66    private int mSecondaryTelephonyPadding;
67    private int mEndPadding;
68    private int mEndPaddingNothingVisible;
69
70    public SignalClusterView(Context context) {
71        this(context, null);
72    }
73
74    public SignalClusterView(Context context, AttributeSet attrs) {
75        this(context, attrs, 0);
76    }
77
78    public SignalClusterView(Context context, AttributeSet attrs, int defStyle) {
79        super(context, attrs, defStyle);
80    }
81
82    public void setNetworkController(NetworkControllerImpl nc) {
83        if (DEBUG) Log.d(TAG, "NetworkController=" + nc);
84        mNC = nc;
85    }
86
87    public void setSecurityController(SecurityController sc) {
88        if (DEBUG) Log.d(TAG, "SecurityController=" + sc);
89        mSC = sc;
90        mSC.addCallback(this);
91        mVpnVisible = mSC.isVpnEnabled();
92    }
93
94    @Override
95    protected void onFinishInflate() {
96        super.onFinishInflate();
97        mWideTypeIconStartPadding = getContext().getResources().getDimensionPixelSize(
98                R.dimen.wide_type_icon_start_padding);
99        mSecondaryTelephonyPadding = getContext().getResources().getDimensionPixelSize(
100                R.dimen.secondary_telephony_padding);
101        mEndPadding = getContext().getResources().getDimensionPixelSize(
102                R.dimen.signal_cluster_battery_padding);
103        mEndPaddingNothingVisible = getContext().getResources().getDimensionPixelSize(
104                R.dimen.no_signal_cluster_battery_padding);
105    }
106
107    @Override
108    protected void onAttachedToWindow() {
109        super.onAttachedToWindow();
110
111        mVpn            = (ImageView) findViewById(R.id.vpn);
112        mWifiGroup      = (ViewGroup) findViewById(R.id.wifi_combo);
113        mWifi           = (ImageView) findViewById(R.id.wifi_signal);
114        mAirplane       = (ImageView) findViewById(R.id.airplane);
115        mNoSims         = (ImageView) findViewById(R.id.no_sims);
116        mWifiAirplaneSpacer =         findViewById(R.id.wifi_airplane_spacer);
117        mWifiSignalSpacer =           findViewById(R.id.wifi_signal_spacer);
118        mMobileSignalGroup = (LinearLayout) findViewById(R.id.mobile_signal_group);
119        for (PhoneState state : mPhoneStates) {
120            mMobileSignalGroup.addView(state.mMobileGroup);
121        }
122
123        apply();
124    }
125
126    @Override
127    protected void onDetachedFromWindow() {
128        mVpn            = null;
129        mWifiGroup      = null;
130        mWifi           = null;
131        mAirplane       = null;
132        mMobileSignalGroup.removeAllViews();
133        mMobileSignalGroup = null;
134
135        super.onDetachedFromWindow();
136    }
137
138    // From SecurityController.
139    @Override
140    public void onStateChanged() {
141        post(new Runnable() {
142            @Override
143            public void run() {
144                mVpnVisible = mSC.isVpnEnabled();
145                apply();
146            }
147        });
148    }
149
150    @Override
151    public void setWifiIndicators(boolean visible, int strengthIcon, String contentDescription) {
152        mWifiVisible = visible;
153        mWifiStrengthId = strengthIcon;
154        mWifiDescription = contentDescription;
155
156        apply();
157    }
158
159    @Override
160    public void setMobileDataIndicators(boolean visible, int strengthIcon, int typeIcon,
161            String contentDescription, String typeContentDescription, boolean isTypeIconWide,
162            int subId) {
163        PhoneState state = getOrInflateState(subId);
164        state.mMobileVisible = visible;
165        state.mMobileStrengthId = strengthIcon;
166        state.mMobileTypeId = typeIcon;
167        state.mMobileDescription = contentDescription;
168        state.mMobileTypeDescription = typeContentDescription;
169        state.mIsMobileTypeIconWide = isTypeIconWide;
170
171        apply();
172    }
173
174    @Override
175    public void setNoSims(boolean show) {
176        mNoSimsVisible = show;
177    }
178
179    @Override
180    public void setSubs(List<SubscriptionInfo> subs) {
181        // Clear out all old subIds.
182        mPhoneStates.clear();
183        if (mMobileSignalGroup != null) {
184            mMobileSignalGroup.removeAllViews();
185        }
186        final int n = subs.size();
187        for (int i = 0; i < n; i++) {
188            inflatePhoneState(subs.get(i).getSubscriptionId());
189        }
190    }
191
192    private PhoneState getOrInflateState(int subId) {
193        for (PhoneState state : mPhoneStates) {
194            if (state.mSubId == subId) {
195                return state;
196            }
197        }
198        return inflatePhoneState(subId);
199    }
200
201    private PhoneState inflatePhoneState(int subId) {
202        PhoneState state = new PhoneState(subId, mContext);
203        if (mMobileSignalGroup != null) {
204            mMobileSignalGroup.addView(state.mMobileGroup);
205        }
206        mPhoneStates.add(state);
207        return state;
208    }
209
210    @Override
211    public void setIsAirplaneMode(boolean is, int airplaneIconId, int contentDescription) {
212        mIsAirplaneMode = is;
213        mAirplaneIconId = airplaneIconId;
214        mAirplaneContentDescription = contentDescription;
215
216        apply();
217    }
218
219    @Override
220    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
221        // Standard group layout onPopulateAccessibilityEvent() implementations
222        // ignore content description, so populate manually
223        if (mWifiVisible && mWifiGroup != null && mWifiGroup.getContentDescription() != null)
224            event.getText().add(mWifiGroup.getContentDescription());
225        for (PhoneState state : mPhoneStates) {
226            state.populateAccessibilityEvent(event);
227        }
228        return super.dispatchPopulateAccessibilityEvent(event);
229    }
230
231    @Override
232    public void onRtlPropertiesChanged(int layoutDirection) {
233        super.onRtlPropertiesChanged(layoutDirection);
234
235        if (mWifi != null) {
236            mWifi.setImageDrawable(null);
237        }
238
239        for (PhoneState state : mPhoneStates) {
240            if (state.mMobile != null) {
241                state.mMobile.setImageDrawable(null);
242            }
243            if (state.mMobileType != null) {
244                state.mMobileType.setImageDrawable(null);
245            }
246        }
247
248        if(mAirplane != null) {
249            mAirplane.setImageDrawable(null);
250        }
251
252        apply();
253    }
254
255    @Override
256    public boolean hasOverlappingRendering() {
257        return false;
258    }
259
260    // Run after each indicator change.
261    private void apply() {
262        if (mWifiGroup == null) return;
263
264        mVpn.setVisibility(mVpnVisible ? View.VISIBLE : View.GONE);
265        if (DEBUG) Log.d(TAG, String.format("vpn: %s", mVpnVisible ? "VISIBLE" : "GONE"));
266        if (mWifiVisible) {
267            mWifi.setImageResource(mWifiStrengthId);
268            mWifiGroup.setContentDescription(mWifiDescription);
269            mWifiGroup.setVisibility(View.VISIBLE);
270        } else {
271            mWifiGroup.setVisibility(View.GONE);
272        }
273
274        if (DEBUG) Log.d(TAG,
275                String.format("wifi: %s sig=%d",
276                    (mWifiVisible ? "VISIBLE" : "GONE"),
277                    mWifiStrengthId));
278
279        boolean anyMobileVisible = false;
280        int firstMobileTypeId = 0;
281        for (PhoneState state : mPhoneStates) {
282            if (state.apply(anyMobileVisible)) {
283                if (!anyMobileVisible) {
284                    firstMobileTypeId = state.mMobileTypeId;
285                    anyMobileVisible = true;
286                }
287            }
288        }
289
290        if (mIsAirplaneMode) {
291            mAirplane.setImageResource(mAirplaneIconId);
292            mAirplane.setContentDescription(mAirplaneContentDescription != 0 ?
293                    mContext.getString(mAirplaneContentDescription) : null);
294            mAirplane.setVisibility(View.VISIBLE);
295        } else {
296            mAirplane.setVisibility(View.GONE);
297        }
298
299        if (mIsAirplaneMode && mWifiVisible) {
300            mWifiAirplaneSpacer.setVisibility(View.VISIBLE);
301        } else {
302            mWifiAirplaneSpacer.setVisibility(View.GONE);
303        }
304
305        if (((anyMobileVisible && firstMobileTypeId != 0) || mNoSimsVisible) && mWifiVisible) {
306            mWifiSignalSpacer.setVisibility(View.VISIBLE);
307        } else {
308            mWifiSignalSpacer.setVisibility(View.GONE);
309        }
310
311        mNoSims.setVisibility(mNoSimsVisible ? View.VISIBLE : View.GONE);
312
313        boolean anythingVisible = mNoSimsVisible || mWifiVisible || mIsAirplaneMode
314                || anyMobileVisible || mVpnVisible;
315        setPaddingRelative(0, 0, anythingVisible ? mEndPadding : mEndPaddingNothingVisible, 0);
316    }
317
318    private class PhoneState {
319        private final int mSubId;
320        private boolean mMobileVisible = false;
321        private int mMobileStrengthId = 0, mMobileTypeId = 0;
322        private boolean mIsMobileTypeIconWide;
323        private String mMobileDescription, mMobileTypeDescription;
324
325        private ViewGroup mMobileGroup;
326        private ImageView mMobile, mMobileType;
327
328        public PhoneState(int subId, Context context) {
329            ViewGroup root = (ViewGroup) LayoutInflater.from(context)
330                    .inflate(R.layout.mobile_signal_group, null);
331            setViews(root);
332            mSubId = subId;
333        }
334
335        public void setViews(ViewGroup root) {
336            mMobileGroup    = root;
337            mMobile         = (ImageView) root.findViewById(R.id.mobile_signal);
338            mMobileType     = (ImageView) root.findViewById(R.id.mobile_type);
339        }
340
341        public boolean apply(boolean isSecondaryIcon) {
342            if (mMobileVisible && !mIsAirplaneMode) {
343                mMobile.setImageResource(mMobileStrengthId);
344                mMobileType.setImageResource(mMobileTypeId);
345                mMobileGroup.setContentDescription(mMobileTypeDescription
346                        + " " + mMobileDescription);
347                mMobileGroup.setVisibility(View.VISIBLE);
348            } else {
349                mMobileGroup.setVisibility(View.GONE);
350            }
351
352            // When this isn't next to wifi, give it some extra padding between the signals.
353            mMobileGroup.setPaddingRelative(isSecondaryIcon ? mSecondaryTelephonyPadding : 0,
354                    0, 0, 0);
355            mMobile.setPaddingRelative(mIsMobileTypeIconWide ? mWideTypeIconStartPadding : 0,
356                    0, 0, 0);
357
358            if (DEBUG) Log.d(TAG, String.format("mobile: %s sig=%d typ=%d",
359                        (mMobileVisible ? "VISIBLE" : "GONE"), mMobileStrengthId, mMobileTypeId));
360
361            mMobileType.setVisibility(mMobileTypeId != 0 ? View.VISIBLE : View.GONE);
362
363            return mMobileVisible;
364        }
365
366        public void populateAccessibilityEvent(AccessibilityEvent event) {
367            if (mMobileVisible && mMobileGroup != null
368                    && mMobileGroup.getContentDescription() != null) {
369                event.getText().add(mMobileGroup.getContentDescription());
370            }
371        }
372    }
373}
374
375