CellularTile.java revision badf986f9444a900e91ca5b84e317a6fb2b9336c
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 android.app.AlertDialog;
20import android.app.AlertDialog.Builder;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.PackageManager;
25import android.content.res.Resources;
26import android.os.SystemProperties;
27import android.service.quicksettings.Tile;
28import android.view.LayoutInflater;
29import android.view.View;
30import android.view.ViewGroup;
31import android.view.WindowManager.LayoutParams;
32import android.widget.Switch;
33import com.android.internal.logging.MetricsLogger;
34import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
35import com.android.settingslib.net.DataUsageController;
36import com.android.systemui.Dependency;
37import com.android.systemui.R;
38import com.android.systemui.R.string;
39import com.android.systemui.plugins.ActivityStarter;
40import com.android.systemui.plugins.qs.DetailAdapter;
41import com.android.systemui.plugins.qs.QSIconView;
42import com.android.systemui.plugins.qs.QSTile.SignalState;
43import com.android.systemui.qs.CellTileView;
44import com.android.systemui.qs.CellTileView.SignalIcon;
45import com.android.systemui.qs.QSHost;
46import com.android.systemui.qs.tileimpl.QSTileImpl;
47import com.android.systemui.statusbar.phone.SystemUIDialog;
48import com.android.systemui.statusbar.policy.KeyguardMonitor;
49import com.android.systemui.statusbar.policy.NetworkController;
50import com.android.systemui.statusbar.policy.NetworkController.IconState;
51import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
52
53/** Quick settings tile: Cellular **/
54public class CellularTile extends QSTileImpl<SignalState> {
55    private static final ComponentName CELLULAR_SETTING_COMPONENT = new ComponentName(
56            "com.android.settings", "com.android.settings.Settings$DataUsageSummaryActivity");
57    private static final ComponentName DATA_PLAN_CELLULAR_COMPONENT = new ComponentName(
58            "com.android.settings", "com.android.settings.Settings$DataPlanUsageSummaryActivity");
59
60    private static final Intent CELLULAR_SETTINGS =
61            new Intent().setComponent(CELLULAR_SETTING_COMPONENT);
62    private static final Intent DATA_PLAN_CELLULAR_SETTINGS =
63            new Intent().setComponent(DATA_PLAN_CELLULAR_COMPONENT);
64
65    private static final String ENABLE_SETTINGS_DATA_PLAN = "enable.settings.data.plan";
66
67    private final NetworkController mController;
68    private final DataUsageController mDataController;
69    private final CellularDetailAdapter mDetailAdapter;
70
71    private final CellSignalCallback mSignalCallback = new CellSignalCallback();
72    private final ActivityStarter mActivityStarter;
73    private final KeyguardMonitor mKeyguardMonitor;
74
75    public CellularTile(QSHost host) {
76        super(host);
77        mController = Dependency.get(NetworkController.class);
78        mActivityStarter = Dependency.get(ActivityStarter.class);
79        mKeyguardMonitor = Dependency.get(KeyguardMonitor.class);
80        mDataController = mController.getMobileDataController();
81        mDetailAdapter = new CellularDetailAdapter();
82    }
83
84    @Override
85    public SignalState newTileState() {
86        return new SignalState();
87    }
88
89    @Override
90    public DetailAdapter getDetailAdapter() {
91        return mDetailAdapter;
92    }
93
94    @Override
95    public void handleSetListening(boolean listening) {
96        if (listening) {
97            mController.addCallback(mSignalCallback);
98        } else {
99            mController.removeCallback(mSignalCallback);
100        }
101    }
102
103    @Override
104    public QSIconView createTileView(Context context) {
105        return new CellTileView(context);
106    }
107
108    @Override
109    public Intent getLongClickIntent() {
110        return getCellularSettingIntent(mContext);
111    }
112
113    @Override
114    protected void handleClick() {
115        if (getState().state == Tile.STATE_UNAVAILABLE) {
116            return;
117        }
118        if (mDataController.isMobileDataEnabled()) {
119            if (mKeyguardMonitor.isSecure() && !mKeyguardMonitor.canSkipBouncer()) {
120                mActivityStarter.postQSRunnableDismissingKeyguard(this::showDisableDialog);
121            } else {
122                mUiHandler.post(this::showDisableDialog);
123            }
124        } else {
125            mDataController.setMobileDataEnabled(true);
126        }
127    }
128
129    private void showDisableDialog() {
130        mHost.collapsePanels();
131        AlertDialog dialog = new Builder(mContext)
132                .setMessage(string.data_usage_disable_mobile)
133                .setNegativeButton(android.R.string.cancel, null)
134                .setPositiveButton(
135                        com.android.internal.R.string.alert_windows_notification_turn_off_action,
136                        (d, w) -> mDataController.setMobileDataEnabled(false))
137                .create();
138        dialog.getWindow().setType(LayoutParams.TYPE_KEYGUARD_DIALOG);
139        SystemUIDialog.setShowForAllUsers(dialog, true);
140        SystemUIDialog.registerDismissListener(dialog);
141        SystemUIDialog.setWindowOnTop(dialog);
142        dialog.show();
143    }
144
145    @Override
146    protected void handleSecondaryClick() {
147        if (mDataController.isMobileDataSupported()) {
148            showDetail(true);
149        } else {
150            mActivityStarter
151                    .postStartActivityDismissingKeyguard(getCellularSettingIntent(mContext),
152                            0 /* delay */);
153        }
154    }
155
156    @Override
157    public CharSequence getTileLabel() {
158        return mContext.getString(R.string.quick_settings_cellular_detail_title);
159    }
160
161    @Override
162    protected void handleUpdateState(SignalState state, Object arg) {
163        CallbackInfo cb = (CallbackInfo) arg;
164        if (cb == null) {
165            cb = mSignalCallback.mInfo;
166        }
167
168        final Resources r = mContext.getResources();
169        state.activityIn = cb.enabled && cb.activityIn;
170        state.activityOut = cb.enabled && cb.activityOut;
171        state.isOverlayIconWide = cb.isDataTypeIconWide;
172        state.overlayIconId = cb.dataTypeIconId;
173
174        state.label = r.getString(R.string.mobile_data);
175
176        final String signalContentDesc = cb.enabled && (cb.mobileSignalIconId > 0)
177                ? cb.signalContentDescription
178                : r.getString(R.string.accessibility_no_signal);
179        if (cb.noSim) {
180            state.contentDescription = state.label;
181        } else {
182            state.contentDescription = signalContentDesc + ", " + state.label;
183        }
184
185        state.expandedAccessibilityClassName = Switch.class.getName();
186        state.value = mDataController.isMobileDataSupported()
187                && mDataController.isMobileDataEnabled();
188
189        if (cb.noSim) {
190            state.icon = ResourceIcon.get(R.drawable.ic_qs_no_sim);
191        } else {
192            state.icon = new SignalIcon(cb.mobileSignalIconId);
193        }
194
195        if (cb.airplaneModeEnabled | cb.noSim) {
196            state.state = Tile.STATE_UNAVAILABLE;
197        } else {
198            state.state = Tile.STATE_ACTIVE;
199        }
200    }
201
202    @Override
203    public int getMetricsCategory() {
204        return MetricsEvent.QS_CELLULAR;
205    }
206
207    @Override
208    public boolean isAvailable() {
209        return mController.hasMobileDataFeature();
210    }
211
212    // Remove the period from the network name
213    public static String removeTrailingPeriod(String string) {
214        if (string == null) return null;
215        final int length = string.length();
216        if (string.endsWith(".")) {
217            return string.substring(0, length - 1);
218        }
219        return string;
220    }
221
222    private static final class CallbackInfo {
223        boolean enabled;
224        boolean wifiEnabled;
225        boolean airplaneModeEnabled;
226        int mobileSignalIconId;
227        String signalContentDescription;
228        int dataTypeIconId;
229        String dataContentDescription;
230        boolean activityIn;
231        boolean activityOut;
232        String enabledDesc;
233        boolean noSim;
234        boolean isDataTypeIconWide;
235        boolean roaming;
236    }
237
238    private final class CellSignalCallback implements SignalCallback {
239        private final CallbackInfo mInfo = new CallbackInfo();
240        @Override
241        public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
242                boolean activityIn, boolean activityOut, String description, boolean isTransient) {
243            mInfo.wifiEnabled = enabled;
244            refreshState(mInfo);
245        }
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.mobileSignalIconId = qsIcon.icon;
257            mInfo.signalContentDescription = qsIcon.contentDescription;
258            mInfo.dataTypeIconId = qsType;
259            mInfo.dataContentDescription = typeContentDescription;
260            mInfo.activityIn = activityIn;
261            mInfo.activityOut = activityOut;
262            mInfo.enabledDesc = description;
263            mInfo.isDataTypeIconWide = qsType != 0 && isWide;
264            mInfo.roaming = roaming;
265            refreshState(mInfo);
266        }
267
268        @Override
269        public void setNoSims(boolean show) {
270            mInfo.noSim = show;
271            if (mInfo.noSim) {
272                // Make sure signal gets cleared out when no sims.
273                mInfo.mobileSignalIconId = 0;
274                mInfo.dataTypeIconId = 0;
275                // Show a No SIMs description to avoid emergency calls message.
276                mInfo.enabled = true;
277                mInfo.enabledDesc = mContext.getString(
278                        R.string.keyguard_missing_sim_message_short);
279                mInfo.signalContentDescription = mInfo.enabledDesc;
280            }
281            refreshState(mInfo);
282        }
283
284        @Override
285        public void setIsAirplaneMode(IconState icon) {
286            mInfo.airplaneModeEnabled = icon.visible;
287            refreshState(mInfo);
288        }
289
290        @Override
291        public void setMobileDataEnabled(boolean enabled) {
292            mDetailAdapter.setMobileDataEnabled(enabled);
293        }
294    }
295
296    static Intent getCellularSettingIntent(Context context) {
297        // TODO(b/62349208): We should replace feature flag check below with data plans
298        // availability check. If the data plans are available we display the data plans usage
299        // summary otherwise we display data usage summary without data plans.
300        boolean isDataPlanFeatureEnabled =
301                SystemProperties.getBoolean(ENABLE_SETTINGS_DATA_PLAN, false /* default */);
302        context.getPackageManager()
303                .setComponentEnabledSetting(
304                        DATA_PLAN_CELLULAR_COMPONENT,
305                        isDataPlanFeatureEnabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
306                                : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
307                        PackageManager.DONT_KILL_APP);
308        context.getPackageManager()
309                .setComponentEnabledSetting(
310                        CELLULAR_SETTING_COMPONENT,
311                        isDataPlanFeatureEnabled ? PackageManager.COMPONENT_ENABLED_STATE_DISABLED
312                                : PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
313                        PackageManager.DONT_KILL_APP);
314        return isDataPlanFeatureEnabled ? DATA_PLAN_CELLULAR_SETTINGS : CELLULAR_SETTINGS;
315    }
316
317    private final class CellularDetailAdapter implements DetailAdapter {
318
319        @Override
320        public CharSequence getTitle() {
321            return mContext.getString(R.string.quick_settings_cellular_detail_title);
322        }
323
324        @Override
325        public Boolean getToggleState() {
326            return mDataController.isMobileDataSupported()
327                    ? mDataController.isMobileDataEnabled()
328                    : null;
329        }
330
331        @Override
332        public Intent getSettingsIntent() {
333            return getCellularSettingIntent(mContext);
334        }
335
336        @Override
337        public void setToggleState(boolean state) {
338            MetricsLogger.action(mContext, MetricsEvent.QS_CELLULAR_TOGGLE, state);
339            mDataController.setMobileDataEnabled(state);
340        }
341
342        @Override
343        public int getMetricsCategory() {
344            return MetricsEvent.QS_DATAUSAGEDETAIL;
345        }
346
347        @Override
348        public View createDetailView(Context context, View convertView, ViewGroup parent) {
349            final DataUsageDetailView v = (DataUsageDetailView) (convertView != null
350                    ? convertView
351                    : LayoutInflater.from(mContext).inflate(R.layout.data_usage, parent, false));
352            final DataUsageController.DataUsageInfo info = mDataController.getDataUsageInfo();
353            if (info == null) return v;
354            v.bind(info);
355            v.findViewById(R.id.roaming_text).setVisibility(mSignalCallback.mInfo.roaming
356                    ? View.VISIBLE : View.INVISIBLE);
357            return v;
358        }
359
360        public void setMobileDataEnabled(boolean enabled) {
361            fireToggleStateChanged(enabled);
362        }
363    }
364}
365