CellularTile.java revision 4a7db95951f95a02a45061071a8837520f1ca818
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 setListening(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 (mDataController.isMobileDataEnabled()) {
116            if (mKeyguardMonitor.isSecure() && !mKeyguardMonitor.canSkipBouncer()) {
117                mActivityStarter.postQSRunnableDismissingKeyguard(this::showDisableDialog);
118            } else {
119                mUiHandler.post(this::showDisableDialog);
120            }
121        } else {
122            mDataController.setMobileDataEnabled(true);
123        }
124    }
125
126    private void showDisableDialog() {
127        mHost.collapsePanels();
128        AlertDialog dialog = new Builder(mContext)
129                .setMessage(string.data_usage_disable_mobile)
130                .setNegativeButton(android.R.string.cancel, null)
131                .setPositiveButton(
132                        com.android.internal.R.string.alert_windows_notification_turn_off_action,
133                        (d, w) -> mDataController.setMobileDataEnabled(false))
134                .create();
135        dialog.getWindow().setType(LayoutParams.TYPE_KEYGUARD_DIALOG);
136        SystemUIDialog.setShowForAllUsers(dialog, true);
137        SystemUIDialog.registerDismissListener(dialog);
138        SystemUIDialog.setWindowOnTop(dialog);
139        dialog.show();
140    }
141
142    @Override
143    protected void handleSecondaryClick() {
144        if (mDataController.isMobileDataSupported()) {
145            showDetail(true);
146        } else {
147            mActivityStarter
148                    .postStartActivityDismissingKeyguard(getCellularSettingIntent(mContext),
149                            0 /* delay */);
150        }
151    }
152
153    @Override
154    public CharSequence getTileLabel() {
155        return mContext.getString(R.string.quick_settings_cellular_detail_title);
156    }
157
158    @Override
159    protected void handleUpdateState(SignalState state, Object arg) {
160        CallbackInfo cb = (CallbackInfo) arg;
161        if (cb == null) {
162            cb = mSignalCallback.mInfo;
163        }
164
165        final Resources r = mContext.getResources();
166        state.activityIn = cb.enabled && cb.activityIn;
167        state.activityOut = cb.enabled && cb.activityOut;
168        state.isOverlayIconWide = cb.isDataTypeIconWide;
169        state.overlayIconId = cb.dataTypeIconId;
170
171        state.label = r.getString(R.string.mobile_data);
172
173        final String signalContentDesc = cb.enabled && (cb.mobileSignalIconId > 0)
174                ? cb.signalContentDescription
175                : r.getString(R.string.accessibility_no_signal);
176        if (cb.noSim) {
177            state.contentDescription = state.label;
178        } else {
179            state.contentDescription = signalContentDesc + ", " + state.label;
180        }
181
182        state.expandedAccessibilityClassName = Switch.class.getName();
183        state.value = mDataController.isMobileDataSupported()
184                && mDataController.isMobileDataEnabled();
185
186        if (cb.noSim) {
187            state.icon = ResourceIcon.get(R.drawable.ic_qs_no_sim);
188        } else {
189            state.icon = new SignalIcon(cb.mobileSignalIconId);
190        }
191
192        if (cb.airplaneModeEnabled | cb.noSim) {
193            state.state = Tile.STATE_INACTIVE;
194        } else {
195            state.state = Tile.STATE_ACTIVE;
196        }
197    }
198
199    @Override
200    public int getMetricsCategory() {
201        return MetricsEvent.QS_CELLULAR;
202    }
203
204    @Override
205    public boolean isAvailable() {
206        return mController.hasMobileDataFeature();
207    }
208
209    // Remove the period from the network name
210    public static String removeTrailingPeriod(String string) {
211        if (string == null) return null;
212        final int length = string.length();
213        if (string.endsWith(".")) {
214            return string.substring(0, length - 1);
215        }
216        return string;
217    }
218
219    private static final class CallbackInfo {
220        boolean enabled;
221        boolean wifiEnabled;
222        boolean airplaneModeEnabled;
223        int mobileSignalIconId;
224        String signalContentDescription;
225        int dataTypeIconId;
226        String dataContentDescription;
227        boolean activityIn;
228        boolean activityOut;
229        String enabledDesc;
230        boolean noSim;
231        boolean isDataTypeIconWide;
232        boolean roaming;
233    }
234
235    private final class CellSignalCallback implements SignalCallback {
236        private final CallbackInfo mInfo = new CallbackInfo();
237        @Override
238        public void setWifiIndicators(boolean enabled, IconState statusIcon, IconState qsIcon,
239                boolean activityIn, boolean activityOut, String description, boolean isTransient) {
240            mInfo.wifiEnabled = enabled;
241            refreshState(mInfo);
242        }
243
244        @Override
245        public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
246                int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
247                String description, boolean isWide, int subId, boolean roaming) {
248            if (qsIcon == null) {
249                // Not data sim, don't display.
250                return;
251            }
252            mInfo.enabled = qsIcon.visible;
253            mInfo.mobileSignalIconId = qsIcon.icon;
254            mInfo.signalContentDescription = qsIcon.contentDescription;
255            mInfo.dataTypeIconId = qsType;
256            mInfo.dataContentDescription = typeContentDescription;
257            mInfo.activityIn = activityIn;
258            mInfo.activityOut = activityOut;
259            mInfo.enabledDesc = description;
260            mInfo.isDataTypeIconWide = qsType != 0 && isWide;
261            mInfo.roaming = roaming;
262            refreshState(mInfo);
263        }
264
265        @Override
266        public void setNoSims(boolean show) {
267            mInfo.noSim = show;
268            if (mInfo.noSim) {
269                // Make sure signal gets cleared out when no sims.
270                mInfo.mobileSignalIconId = 0;
271                mInfo.dataTypeIconId = 0;
272                // Show a No SIMs description to avoid emergency calls message.
273                mInfo.enabled = true;
274                mInfo.enabledDesc = mContext.getString(
275                        R.string.keyguard_missing_sim_message_short);
276                mInfo.signalContentDescription = mInfo.enabledDesc;
277            }
278            refreshState(mInfo);
279        }
280
281        @Override
282        public void setIsAirplaneMode(IconState icon) {
283            mInfo.airplaneModeEnabled = icon.visible;
284            refreshState(mInfo);
285        }
286
287        @Override
288        public void setMobileDataEnabled(boolean enabled) {
289            mDetailAdapter.setMobileDataEnabled(enabled);
290        }
291    }
292
293    static Intent getCellularSettingIntent(Context context) {
294        // TODO(b/62349208): We should replace feature flag check below with data plans
295        // availability check. If the data plans are available we display the data plans usage
296        // summary otherwise we display data usage summary without data plans.
297        boolean isDataPlanFeatureEnabled =
298                SystemProperties.getBoolean(ENABLE_SETTINGS_DATA_PLAN, false /* default */);
299        context.getPackageManager()
300                .setComponentEnabledSetting(
301                        DATA_PLAN_CELLULAR_COMPONENT,
302                        isDataPlanFeatureEnabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
303                                : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
304                        PackageManager.DONT_KILL_APP);
305        context.getPackageManager()
306                .setComponentEnabledSetting(
307                        CELLULAR_SETTING_COMPONENT,
308                        isDataPlanFeatureEnabled ? PackageManager.COMPONENT_ENABLED_STATE_DISABLED
309                                : PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
310                        PackageManager.DONT_KILL_APP);
311        return isDataPlanFeatureEnabled ? DATA_PLAN_CELLULAR_SETTINGS : CELLULAR_SETTINGS;
312    }
313
314    private final class CellularDetailAdapter implements DetailAdapter {
315
316        @Override
317        public CharSequence getTitle() {
318            return mContext.getString(R.string.quick_settings_cellular_detail_title);
319        }
320
321        @Override
322        public Boolean getToggleState() {
323            return mDataController.isMobileDataSupported()
324                    ? mDataController.isMobileDataEnabled()
325                    : null;
326        }
327
328        @Override
329        public Intent getSettingsIntent() {
330            return getCellularSettingIntent(mContext);
331        }
332
333        @Override
334        public void setToggleState(boolean state) {
335            MetricsLogger.action(mContext, MetricsEvent.QS_CELLULAR_TOGGLE, state);
336            mDataController.setMobileDataEnabled(state);
337        }
338
339        @Override
340        public int getMetricsCategory() {
341            return MetricsEvent.QS_DATAUSAGEDETAIL;
342        }
343
344        @Override
345        public View createDetailView(Context context, View convertView, ViewGroup parent) {
346            final DataUsageDetailView v = (DataUsageDetailView) (convertView != null
347                    ? convertView
348                    : LayoutInflater.from(mContext).inflate(R.layout.data_usage, parent, false));
349            final DataUsageController.DataUsageInfo info = mDataController.getDataUsageInfo();
350            if (info == null) return v;
351            v.bind(info);
352            v.findViewById(R.id.roaming_text).setVisibility(mSignalCallback.mInfo.roaming
353                    ? View.VISIBLE : View.INVISIBLE);
354            return v;
355        }
356
357        public void setMobileDataEnabled(boolean enabled) {
358            fireToggleStateChanged(enabled);
359        }
360    }
361}
362