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