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 android.app.AlertDialog;
     20 import android.app.AlertDialog.Builder;
     21 import android.content.ComponentName;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.pm.PackageManager;
     25 import android.content.res.Resources;
     26 import android.os.SystemProperties;
     27 import android.service.quicksettings.Tile;
     28 import android.view.LayoutInflater;
     29 import android.view.View;
     30 import android.view.ViewGroup;
     31 import android.view.WindowManager.LayoutParams;
     32 import android.widget.Switch;
     33 import com.android.internal.logging.MetricsLogger;
     34 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
     35 import com.android.settingslib.net.DataUsageController;
     36 import com.android.systemui.Dependency;
     37 import com.android.systemui.R;
     38 import com.android.systemui.R.string;
     39 import com.android.systemui.plugins.ActivityStarter;
     40 import com.android.systemui.plugins.qs.DetailAdapter;
     41 import com.android.systemui.plugins.qs.QSIconView;
     42 import com.android.systemui.plugins.qs.QSTile.SignalState;
     43 import com.android.systemui.qs.CellTileView;
     44 import com.android.systemui.qs.CellTileView.SignalIcon;
     45 import com.android.systemui.qs.QSHost;
     46 import com.android.systemui.qs.tileimpl.QSTileImpl;
     47 import com.android.systemui.statusbar.phone.SystemUIDialog;
     48 import com.android.systemui.statusbar.policy.KeyguardMonitor;
     49 import com.android.systemui.statusbar.policy.NetworkController;
     50 import com.android.systemui.statusbar.policy.NetworkController.IconState;
     51 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
     52 
     53 /** Quick settings tile: Cellular **/
     54 public 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 (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, boolean simDetected) {
    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