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