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