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