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