1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.settings.datausage; 16 17 import android.app.settings.SettingsEnums; 18 import android.content.Context; 19 import android.content.DialogInterface; 20 import android.database.ContentObserver; 21 import android.net.NetworkTemplate; 22 import android.net.Uri; 23 import android.os.Handler; 24 import android.os.Looper; 25 import android.os.Parcel; 26 import android.os.Parcelable; 27 import android.provider.Settings.Global; 28 import android.telephony.SubscriptionInfo; 29 import android.telephony.SubscriptionManager; 30 import android.telephony.TelephonyManager; 31 import android.util.AttributeSet; 32 import android.util.Log; 33 import android.view.View; 34 import android.widget.Checkable; 35 36 import androidx.annotation.VisibleForTesting; 37 import androidx.appcompat.app.AlertDialog.Builder; 38 import androidx.core.content.res.TypedArrayUtils; 39 import androidx.preference.PreferenceViewHolder; 40 41 import com.android.settings.R; 42 import com.android.settings.Utils; 43 import com.android.settings.overlay.FeatureFactory; 44 import com.android.settingslib.CustomDialogPreferenceCompat; 45 46 import java.util.List; 47 48 public class CellDataPreference extends CustomDialogPreferenceCompat implements TemplatePreference { 49 50 private static final String TAG = "CellDataPreference"; 51 52 public int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; 53 public boolean mChecked; 54 public boolean mMultiSimDialog; 55 private TelephonyManager mTelephonyManager; 56 @VisibleForTesting 57 SubscriptionManager mSubscriptionManager; 58 CellDataPreference(Context context, AttributeSet attrs)59 public CellDataPreference(Context context, AttributeSet attrs) { 60 super(context, attrs, TypedArrayUtils.getAttr(context, 61 androidx.preference.R.attr.switchPreferenceStyle, 62 android.R.attr.switchPreferenceStyle)); 63 } 64 65 @Override onRestoreInstanceState(Parcelable s)66 protected void onRestoreInstanceState(Parcelable s) { 67 CellDataState state = (CellDataState) s; 68 super.onRestoreInstanceState(state.getSuperState()); 69 mTelephonyManager = TelephonyManager.from(getContext()); 70 mChecked = state.mChecked; 71 mMultiSimDialog = state.mMultiSimDialog; 72 if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 73 mSubId = state.mSubId; 74 setKey(getKey() + mSubId); 75 } 76 notifyChanged(); 77 } 78 79 @Override onSaveInstanceState()80 protected Parcelable onSaveInstanceState() { 81 CellDataState state = new CellDataState(super.onSaveInstanceState()); 82 state.mChecked = mChecked; 83 state.mMultiSimDialog = mMultiSimDialog; 84 state.mSubId = mSubId; 85 return state; 86 } 87 88 @Override onAttached()89 public void onAttached() { 90 super.onAttached(); 91 mDataStateListener.setListener(true, mSubId, getContext()); 92 if (mSubscriptionManager!= null) { 93 mSubscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener); 94 } 95 } 96 97 @Override onDetached()98 public void onDetached() { 99 mDataStateListener.setListener(false, mSubId, getContext()); 100 if (mSubscriptionManager!= null) { 101 mSubscriptionManager.removeOnSubscriptionsChangedListener( 102 mOnSubscriptionsChangeListener); 103 } 104 super.onDetached(); 105 } 106 107 @Override setTemplate(NetworkTemplate template, int subId, NetworkServices services)108 public void setTemplate(NetworkTemplate template, int subId, NetworkServices services) { 109 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 110 throw new IllegalArgumentException("CellDataPreference needs a SubscriptionInfo"); 111 } 112 mSubscriptionManager = SubscriptionManager.from(getContext()); 113 mTelephonyManager = TelephonyManager.from(getContext()); 114 115 mSubscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangeListener); 116 117 if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 118 mSubId = subId; 119 setKey(getKey() + subId); 120 } 121 updateEnabled(); 122 updateChecked(); 123 } 124 updateChecked()125 private void updateChecked() { 126 setChecked(mTelephonyManager.getDataEnabled(mSubId)); 127 } 128 updateEnabled()129 private void updateEnabled() { 130 // If this subscription is not active, for example, SIM card is taken out, we disable 131 // the button. 132 setEnabled(mSubscriptionManager.getActiveSubscriptionInfo(mSubId) != null); 133 } 134 135 @Override performClick(View view)136 protected void performClick(View view) { 137 final Context context = getContext(); 138 FeatureFactory.getFactory(context).getMetricsFeatureProvider() 139 .action(context, SettingsEnums.ACTION_CELL_DATA_TOGGLE, !mChecked); 140 final SubscriptionInfo currentSir = mSubscriptionManager.getActiveSubscriptionInfo( 141 mSubId); 142 final SubscriptionInfo nextSir = mSubscriptionManager.getDefaultDataSubscriptionInfo(); 143 if (mChecked) { 144 // If the device is single SIM or is enabling data on the active data SIM then forgo 145 // the pop-up. 146 if (!Utils.showSimCardTile(getContext()) || 147 (nextSir != null && currentSir != null && 148 currentSir.getSubscriptionId() == nextSir.getSubscriptionId())) { 149 setMobileDataEnabled(false); 150 if (nextSir != null && currentSir != null && 151 currentSir.getSubscriptionId() == nextSir.getSubscriptionId()) { 152 disableDataForOtherSubscriptions(mSubId); 153 } 154 return; 155 } 156 // disabling data; show confirmation dialog which eventually 157 // calls setMobileDataEnabled() once user confirms. 158 mMultiSimDialog = false; 159 super.performClick(view); 160 } else { 161 // If we are showing the Sim Card tile then we are a Multi-Sim device. 162 if (Utils.showSimCardTile(getContext())) { 163 mMultiSimDialog = true; 164 if (nextSir != null && currentSir != null && 165 currentSir.getSubscriptionId() == nextSir.getSubscriptionId()) { 166 setMobileDataEnabled(true); 167 disableDataForOtherSubscriptions(mSubId); 168 return; 169 } 170 super.performClick(view); 171 } else { 172 setMobileDataEnabled(true); 173 } 174 } 175 } 176 setMobileDataEnabled(boolean enabled)177 private void setMobileDataEnabled(boolean enabled) { 178 if (DataUsageSummary.LOGD) Log.d(TAG, "setMobileDataEnabled(" + enabled + "," 179 + mSubId + ")"); 180 mTelephonyManager.setDataEnabled(mSubId, enabled); 181 setChecked(enabled); 182 } 183 setChecked(boolean checked)184 private void setChecked(boolean checked) { 185 if (mChecked == checked) return; 186 mChecked = checked; 187 notifyChanged(); 188 } 189 190 @Override onBindViewHolder(PreferenceViewHolder holder)191 public void onBindViewHolder(PreferenceViewHolder holder) { 192 super.onBindViewHolder(holder); 193 View switchView = holder.findViewById(android.R.id.switch_widget); 194 switchView.setClickable(false); 195 ((Checkable) switchView).setChecked(mChecked); 196 } 197 198 @Override onPrepareDialogBuilder(Builder builder, DialogInterface.OnClickListener listener)199 protected void onPrepareDialogBuilder(Builder builder, 200 DialogInterface.OnClickListener listener) { 201 if (mMultiSimDialog) { 202 showMultiSimDialog(builder, listener); 203 } else { 204 showDisableDialog(builder, listener); 205 } 206 } 207 showDisableDialog(Builder builder, DialogInterface.OnClickListener listener)208 private void showDisableDialog(Builder builder, 209 DialogInterface.OnClickListener listener) { 210 builder.setTitle(null) 211 .setMessage(R.string.data_usage_disable_mobile) 212 .setPositiveButton(android.R.string.ok, listener) 213 .setNegativeButton(android.R.string.cancel, null); 214 } 215 showMultiSimDialog(Builder builder, DialogInterface.OnClickListener listener)216 private void showMultiSimDialog(Builder builder, 217 DialogInterface.OnClickListener listener) { 218 final SubscriptionInfo currentSir = mSubscriptionManager.getActiveSubscriptionInfo(mSubId); 219 final SubscriptionInfo nextSir = mSubscriptionManager.getDefaultDataSubscriptionInfo(); 220 221 final String previousName = (nextSir == null) 222 ? getContext().getResources().getString(R.string.sim_selection_required_pref) 223 : nextSir.getDisplayName().toString(); 224 225 builder.setTitle(R.string.sim_change_data_title); 226 builder.setMessage(getContext().getString(R.string.sim_change_data_message, 227 String.valueOf(currentSir != null ? currentSir.getDisplayName() : null), 228 previousName)); 229 230 builder.setPositiveButton(R.string.okay, listener); 231 builder.setNegativeButton(R.string.cancel, null); 232 } 233 disableDataForOtherSubscriptions(int subId)234 private void disableDataForOtherSubscriptions(int subId) { 235 List<SubscriptionInfo> subInfoList = mSubscriptionManager 236 .getActiveSubscriptionInfoList(true); 237 if (subInfoList != null) { 238 for (SubscriptionInfo subInfo : subInfoList) { 239 if (subInfo.getSubscriptionId() != subId) { 240 mTelephonyManager.setDataEnabled(subInfo.getSubscriptionId(), false); 241 } 242 } 243 } 244 } 245 246 @Override onClick(DialogInterface dialog, int which)247 protected void onClick(DialogInterface dialog, int which) { 248 if (which != DialogInterface.BUTTON_POSITIVE) { 249 return; 250 } 251 if (mMultiSimDialog) { 252 mSubscriptionManager.setDefaultDataSubId(mSubId); 253 setMobileDataEnabled(true); 254 disableDataForOtherSubscriptions(mSubId); 255 } else { 256 // TODO: extend to modify policy enabled flag. 257 setMobileDataEnabled(false); 258 } 259 } 260 261 @VisibleForTesting 262 final SubscriptionManager.OnSubscriptionsChangedListener mOnSubscriptionsChangeListener 263 = new SubscriptionManager.OnSubscriptionsChangedListener() { 264 @Override 265 public void onSubscriptionsChanged() { 266 if (DataUsageSummary.LOGD) { 267 Log.d(TAG, "onSubscriptionsChanged"); 268 } 269 updateEnabled(); 270 } 271 }; 272 273 private final DataStateListener mDataStateListener = new DataStateListener() { 274 @Override 275 public void onChange(boolean selfChange) { 276 updateChecked(); 277 } 278 }; 279 280 public abstract static class DataStateListener extends ContentObserver { DataStateListener()281 public DataStateListener() { 282 super(new Handler(Looper.getMainLooper())); 283 } 284 setListener(boolean listening, int subId, Context context)285 public void setListener(boolean listening, int subId, Context context) { 286 if (listening) { 287 Uri uri = Global.getUriFor(Global.MOBILE_DATA); 288 if (TelephonyManager.getDefault().getSimCount() != 1) { 289 uri = Global.getUriFor(Global.MOBILE_DATA + subId); 290 } 291 context.getContentResolver().registerContentObserver(uri, false, this); 292 } else { 293 context.getContentResolver().unregisterContentObserver(this); 294 } 295 } 296 } 297 298 public static class CellDataState extends BaseSavedState { 299 public int mSubId; 300 public boolean mChecked; 301 public boolean mMultiSimDialog; 302 CellDataState(Parcelable base)303 public CellDataState(Parcelable base) { 304 super(base); 305 } 306 CellDataState(Parcel source)307 public CellDataState(Parcel source) { 308 super(source); 309 mChecked = source.readByte() != 0; 310 mMultiSimDialog = source.readByte() != 0; 311 mSubId = source.readInt(); 312 } 313 314 @Override writeToParcel(Parcel dest, int flags)315 public void writeToParcel(Parcel dest, int flags) { 316 super.writeToParcel(dest, flags); 317 dest.writeByte((byte) (mChecked ? 1 : 0)); 318 dest.writeByte((byte) (mMultiSimDialog ? 1 : 0)); 319 dest.writeInt(mSubId); 320 } 321 322 public static final Creator<CellDataState> CREATOR = new Creator<CellDataState>() { 323 @Override 324 public CellDataState createFromParcel(Parcel source) { 325 return new CellDataState(source); 326 } 327 328 @Override 329 public CellDataState[] newArray(int size) { 330 return new CellDataState[size]; 331 } 332 }; 333 } 334 } 335