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