CellularTile.java revision cb87872137e69811142aaaafa16a947a5ae8a71a
1/*
2 * Copyright (C) 2014 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.qs.tiles;
18
19import static com.android.systemui.Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA;
20
21import android.app.AlertDialog;
22import android.app.AlertDialog.Builder;
23import android.content.Context;
24import android.content.Intent;
25import android.content.res.Resources;
26import android.provider.Settings;
27import android.service.quicksettings.Tile;
28import android.text.TextUtils;
29import android.view.LayoutInflater;
30import android.view.View;
31import android.view.ViewGroup;
32import android.view.WindowManager.LayoutParams;
33import android.widget.Switch;
34
35import com.android.internal.logging.MetricsLogger;
36import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
37import com.android.settingslib.net.DataUsageController;
38import com.android.systemui.Dependency;
39import com.android.systemui.Prefs;
40import com.android.systemui.R;
41import com.android.systemui.plugins.ActivityStarter;
42import com.android.systemui.plugins.qs.DetailAdapter;
43import com.android.systemui.plugins.qs.QSIconView;
44import com.android.systemui.plugins.qs.QSTile.SignalState;
45import com.android.systemui.qs.CellTileView;
46import com.android.systemui.qs.QSHost;
47import com.android.systemui.qs.tileimpl.QSTileImpl;
48import com.android.systemui.statusbar.phone.SystemUIDialog;
49import com.android.systemui.statusbar.policy.KeyguardMonitor;
50import com.android.systemui.statusbar.policy.NetworkController;
51import com.android.systemui.statusbar.policy.NetworkController.IconState;
52import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
53
54/** Quick settings tile: Cellular **/
55public class CellularTile extends QSTileImpl<SignalState> {
56    private static final String ENABLE_SETTINGS_DATA_PLAN = "enable.settings.data.plan";
57
58    private final NetworkController mController;
59    private final DataUsageController mDataController;
60    private final CellularDetailAdapter mDetailAdapter;
61
62    private final CellSignalCallback mSignalCallback = new CellSignalCallback();
63    private final ActivityStarter mActivityStarter;
64    private final KeyguardMonitor mKeyguardMonitor;
65
66    public CellularTile(QSHost host) {
67        super(host);
68        mController = Dependency.get(NetworkController.class);
69        mActivityStarter = Dependency.get(ActivityStarter.class);
70        mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
71        mDataController = mController.getMobileDataController();
72        mDetailAdapter = new CellularDetailAdapter();
73    }
74
75    @Override
76    public SignalState newTileState() {
77        return new SignalState();
78    }
79
80    @Override
81    public DetailAdapter getDetailAdapter() {
82        return mDetailAdapter;
83    }
84
85    @Override
86    public void handleSetListening(boolean listening) {
87        if (listening) {
88            mController.addCallback(mSignalCallback);
89        } else {
90            mController.removeCallback(mSignalCallback);
91        }
92    }
93
94    @Override
95    public QSIconView createTileView(Context context) {
96        return new CellTileView(context);
97    }
98
99    @Override
100    public Intent getLongClickIntent() {
101        return getCellularSettingIntent();
102    }
103
104    @Override
105    protected void handleClick() {
106        if (getState().state == Tile.STATE_UNAVAILABLE) {
107            return;
108        }
109        if (mDataController.isMobileDataEnabled()) {
110            if (mKeyguardMonitor.isSecure() && !mKeyguardMonitor.canSkipBouncer()) {
111                mActivityStarter.postQSRunnableDismissingKeyguard(this::maybeShowDisableDialog);
112            } else {
113                mUiHandler.post(this::maybeShowDisableDialog);
114            }
115        } else {
116            mDataController.setMobileDataEnabled(true);
117        }
118    }
119
120    private void maybeShowDisableDialog() {
121        if (Prefs.getBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, false)) {
122            // Directly turn off mobile data if the user has seen the dialog before.
123            mDataController.setMobileDataEnabled(false);
124            return;
125        }
126        mHost.collapsePanels();
127        String carrierName = mController.getMobileDataNetworkName();
128        if (TextUtils.isEmpty(carrierName)) {
129            carrierName = mContext.getString(R.string.mobile_data_disable_message_default_carrier);
130        }
131        AlertDialog dialog = new Builder(mContext)
132                .setTitle(R.string.mobile_data_disable_title)
133                .setMessage(mContext.getString(R.string.mobile_data_disable_message, carrierName))
134                .setNegativeButton(android.R.string.cancel, null)
135                .setPositiveButton(
136                        com.android.internal.R.string.alert_windows_notification_turn_off_action,
137                        (d, w) -> {
138                            mDataController.setMobileDataEnabled(false);
139                            Prefs.putBoolean(mContext, QS_HAS_TURNED_OFF_MOBILE_DATA, true);
140                        })
141                .create();
142        dialog.getWindow().setType(LayoutParams.TYPE_KEYGUARD_DIALOG);
143        SystemUIDialog.setShowForAllUsers(dialog, true);
144        SystemUIDialog.registerDismissListener(dialog);
145        SystemUIDialog.setWindowOnTop(dialog);
146        dialog.show();
147    }
148
149    @Override
150    protected void handleSecondaryClick() {
151        if (mDataController.isMobileDataSupported()) {
152            showDetail(true);
153        } else {
154            mActivityStarter
155                    .postStartActivityDismissingKeyguard(getCellularSettingIntent(),0 /* delay */);
156        }
157    }
158
159    @Override
160    public CharSequence getTileLabel() {
161        return mContext.getString(R.string.quick_settings_cellular_detail_title);
162    }
163
164    @Override
165    protected void handleUpdateState(SignalState state, Object arg) {
166        CallbackInfo cb = (CallbackInfo) arg;
167        if (cb == null) {
168            cb = mSignalCallback.mInfo;
169        }
170
171        final Resources r = mContext.getResources();
172        state.activityIn = cb.enabled && cb.activityIn;
173        state.activityOut = cb.enabled && cb.activityOut;
174        state.label = r.getString(R.string.mobile_data);
175        boolean mobileDataEnabled = mDataController.isMobileDataSupported()
176                && mDataController.isMobileDataEnabled();
177        state.value = mobileDataEnabled;
178        state.expandedAccessibilityClassName = Switch.class.getName();
179        if (cb.noSim) {
180            state.icon = ResourceIcon.get(R.drawable.ic_qs_no_sim);
181        } else {
182            state.icon = ResourceIcon.get(R.drawable.ic_swap_vert);
183        }
184
185        if (cb.noSim) {
186            state.state = Tile.STATE_UNAVAILABLE;
187            state.secondaryLabel = r.getString(R.string.keyguard_missing_sim_message_short);
188        } else if (cb.airplaneModeEnabled) {
189            state.state = Tile.STATE_UNAVAILABLE;
190            state.secondaryLabel = r.getString(R.string.status_bar_airplane);
191        } else if (mobileDataEnabled) {
192            state.state = Tile.STATE_ACTIVE;
193            state.secondaryLabel = getMobileDataDescription(cb);
194        } else {
195            state.state = Tile.STATE_INACTIVE;
196            state.secondaryLabel = r.getString(R.string.cell_data_off);
197        }
198
199
200        // TODO(b/77881974): Instead of switching out the description via a string check for
201        // we need to have two strings provided by the MobileIconGroup.
202        final CharSequence contentDescriptionSuffix;
203        if (state.state == Tile.STATE_INACTIVE) {
204            contentDescriptionSuffix = r.getString(R.string.cell_data_off_content_description);
205        } else {
206            contentDescriptionSuffix = state.secondaryLabel;
207        }
208
209        state.contentDescription = state.label + ", " + contentDescriptionSuffix;
210    }
211
212    private CharSequence getMobileDataDescription(CallbackInfo cb) {
213        if (cb.roaming && !TextUtils.isEmpty(cb.dataContentDescription)) {
214            String roaming = mContext.getString(R.string.data_connection_roaming);
215            String dataDescription = cb.dataContentDescription;
216            return mContext.getString(R.string.mobile_data_text_format, roaming, dataDescription);
217        }
218        if (cb.roaming) {
219            return mContext.getString(R.string.data_connection_roaming);
220        }
221        return cb.dataContentDescription;
222    }
223
224    @Override
225    public int getMetricsCategory() {
226        return MetricsEvent.QS_CELLULAR;
227    }
228
229    @Override
230    public boolean isAvailable() {
231        return mController.hasMobileDataFeature();
232    }
233
234    private static final class CallbackInfo {
235        boolean enabled;
236        boolean airplaneModeEnabled;
237        String dataContentDescription;
238        boolean activityIn;
239        boolean activityOut;
240        boolean noSim;
241        boolean roaming;
242    }
243
244    private final class CellSignalCallback implements SignalCallback {
245        private final CallbackInfo mInfo = new CallbackInfo();
246
247        @Override
248        public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
249                int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
250                String description, boolean isWide, int subId, boolean roaming) {
251            if (qsIcon == null) {
252                // Not data sim, don't display.
253                return;
254            }
255            mInfo.enabled = qsIcon.visible;
256            mInfo.dataContentDescription = typeContentDescription;
257            mInfo.activityIn = activityIn;
258            mInfo.activityOut = activityOut;
259            mInfo.roaming = roaming;
260            refreshState(mInfo);
261        }
262
263        @Override
264        public void setNoSims(boolean show, boolean simDetected) {
265            mInfo.noSim = show;
266            refreshState(mInfo);
267        }
268
269        @Override
270        public void setIsAirplaneMode(IconState icon) {
271            mInfo.airplaneModeEnabled = icon.visible;
272            refreshState(mInfo);
273        }
274
275        @Override
276        public void setMobileDataEnabled(boolean enabled) {
277            mDetailAdapter.setMobileDataEnabled(enabled);
278        }
279    }
280
281    static Intent getCellularSettingIntent() {
282        return new Intent(Settings.ACTION_DATA_USAGE_SETTINGS);
283    }
284
285    private final class CellularDetailAdapter implements DetailAdapter {
286
287        @Override
288        public CharSequence getTitle() {
289            return mContext.getString(R.string.quick_settings_cellular_detail_title);
290        }
291
292        @Override
293        public Boolean getToggleState() {
294            return mDataController.isMobileDataSupported()
295                    ? mDataController.isMobileDataEnabled()
296                    : null;
297        }
298
299        @Override
300        public Intent getSettingsIntent() {
301            return getCellularSettingIntent();
302        }
303
304        @Override
305        public void setToggleState(boolean state) {
306            MetricsLogger.action(mContext, MetricsEvent.QS_CELLULAR_TOGGLE, state);
307            mDataController.setMobileDataEnabled(state);
308        }
309
310        @Override
311        public int getMetricsCategory() {
312            return MetricsEvent.QS_DATAUSAGEDETAIL;
313        }
314
315        @Override
316        public View createDetailView(Context context, View convertView, ViewGroup parent) {
317            final DataUsageDetailView v = (DataUsageDetailView) (convertView != null
318                    ? convertView
319                    : LayoutInflater.from(mContext).inflate(R.layout.data_usage, parent, false));
320            final DataUsageController.DataUsageInfo info = mDataController.getDataUsageInfo();
321            if (info == null) return v;
322            v.bind(info);
323            v.findViewById(R.id.roaming_text).setVisibility(mSignalCallback.mInfo.roaming
324                    ? View.VISIBLE : View.INVISIBLE);
325            return v;
326        }
327
328        public void setMobileDataEnabled(boolean enabled) {
329            fireToggleStateChanged(enabled);
330        }
331    }
332}
333