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.net.NetworkTemplate;
21 import android.os.Parcel;
22 import android.os.Parcelable;
23 import android.telephony.SubscriptionInfo;
24 import android.telephony.SubscriptionManager;
25 import android.telephony.TelephonyManager;
26 import android.util.AttributeSet;
27 import android.util.Log;
28 import android.view.View;
29 import android.widget.Checkable;
30 
31 import androidx.annotation.VisibleForTesting;
32 import androidx.appcompat.app.AlertDialog.Builder;
33 import androidx.core.content.res.TypedArrayUtils;
34 import androidx.preference.PreferenceViewHolder;
35 
36 import com.android.settings.R;
37 import com.android.settings.network.MobileDataEnabledListener;
38 import com.android.settings.network.ProxySubscriptionManager;
39 import com.android.settings.network.SubscriptionUtil;
40 import com.android.settings.overlay.FeatureFactory;
41 import com.android.settingslib.CustomDialogPreferenceCompat;
42 
43 /**
44  * Preference of cellular data control within Data Usage
45  */
46 public class CellDataPreference extends CustomDialogPreferenceCompat
47         implements TemplatePreference, MobileDataEnabledListener.Client {
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 MobileDataEnabledListener mDataStateListener;
55 
CellDataPreference(Context context, AttributeSet attrs)56     public CellDataPreference(Context context, AttributeSet attrs) {
57         super(context, attrs, TypedArrayUtils.getAttr(context,
58                 androidx.preference.R.attr.switchPreferenceStyle,
59                 android.R.attr.switchPreferenceStyle));
60         mDataStateListener = new MobileDataEnabledListener(context, this);
61     }
62 
63     @Override
onRestoreInstanceState(Parcelable s)64     protected void onRestoreInstanceState(Parcelable s) {
65         final CellDataState state = (CellDataState) s;
66         super.onRestoreInstanceState(state.getSuperState());
67         mChecked = state.mChecked;
68         mMultiSimDialog = state.mMultiSimDialog;
69         if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
70             mSubId = state.mSubId;
71             setKey(getKey() + mSubId);
72         }
73         notifyChanged();
74     }
75 
76     @Override
onSaveInstanceState()77     protected Parcelable onSaveInstanceState() {
78         final CellDataState state = new CellDataState(super.onSaveInstanceState());
79         state.mChecked = mChecked;
80         state.mMultiSimDialog = mMultiSimDialog;
81         state.mSubId = mSubId;
82         return state;
83     }
84 
85     @Override
onAttached()86     public void onAttached() {
87         super.onAttached();
88         mDataStateListener.start(mSubId);
89         getProxySubscriptionManager()
90                 .addActiveSubscriptionsListener(mOnSubscriptionsChangeListener);
91     }
92 
93     @Override
onDetached()94     public void onDetached() {
95         mDataStateListener.stop();
96         getProxySubscriptionManager()
97                 .removeActiveSubscriptionsListener(mOnSubscriptionsChangeListener);
98         super.onDetached();
99     }
100 
101     @Override
setTemplate(NetworkTemplate template, int subId, NetworkServices services)102     public void setTemplate(NetworkTemplate template, int subId, NetworkServices services) {
103         if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
104             throw new IllegalArgumentException("CellDataPreference needs a SubscriptionInfo");
105         }
106 
107         getProxySubscriptionManager()
108                 .addActiveSubscriptionsListener(mOnSubscriptionsChangeListener);
109 
110         if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
111             mSubId = subId;
112             setKey(getKey() + subId);
113         }
114         updateEnabled();
115         updateChecked();
116     }
117 
118     @VisibleForTesting
getProxySubscriptionManager()119     ProxySubscriptionManager getProxySubscriptionManager() {
120         return ProxySubscriptionManager.getInstance(getContext());
121     }
122 
123     @VisibleForTesting
getActiveSubscriptionInfo(int subId)124     SubscriptionInfo getActiveSubscriptionInfo(int subId) {
125         return getProxySubscriptionManager().getActiveSubscriptionInfo(subId);
126     }
127 
updateChecked()128     private void updateChecked() {
129         setChecked(getContext().getSystemService(TelephonyManager.class).getDataEnabled(mSubId));
130     }
131 
updateEnabled()132     private void updateEnabled() {
133         // If this subscription is not active, for example, SIM card is taken out, we disable
134         // the button.
135         setEnabled(getActiveSubscriptionInfo(mSubId) != null);
136     }
137 
138     @Override
performClick(View view)139     protected void performClick(View view) {
140         final Context context = getContext();
141         FeatureFactory.getFactory(context).getMetricsFeatureProvider()
142                 .action(context, SettingsEnums.ACTION_CELL_DATA_TOGGLE, !mChecked);
143         final SubscriptionInfo currentSir = getActiveSubscriptionInfo(mSubId);
144         final SubscriptionInfo nextSir = getActiveSubscriptionInfo(
145                 SubscriptionManager.getDefaultDataSubscriptionId());
146         if (mChecked) {
147             setMobileDataEnabled(false);
148             if (nextSir != null && currentSir != null
149                     && currentSir.getSubscriptionId() == nextSir.getSubscriptionId()) {
150                 disableDataForOtherSubscriptions(mSubId);
151             }
152         } else {
153             setMobileDataEnabled(true);
154         }
155     }
156 
setMobileDataEnabled(boolean enabled)157     private void setMobileDataEnabled(boolean enabled) {
158         if (DataUsageSummary.LOGD) Log.d(TAG, "setMobileDataEnabled(" + enabled + ","
159                 + mSubId + ")");
160         getContext().getSystemService(TelephonyManager.class).setDataEnabled(mSubId, enabled);
161         setChecked(enabled);
162     }
163 
setChecked(boolean checked)164     private void setChecked(boolean checked) {
165         if (mChecked == checked) return;
166         mChecked = checked;
167         notifyChanged();
168     }
169 
170     @Override
onBindViewHolder(PreferenceViewHolder holder)171     public void onBindViewHolder(PreferenceViewHolder holder) {
172         super.onBindViewHolder(holder);
173         final View switchView = holder.findViewById(android.R.id.switch_widget);
174         switchView.setClickable(false);
175         ((Checkable) switchView).setChecked(mChecked);
176     }
177 
178     @Override
onPrepareDialogBuilder(Builder builder, DialogInterface.OnClickListener listener)179     protected void onPrepareDialogBuilder(Builder builder,
180             DialogInterface.OnClickListener listener) {
181         if (mMultiSimDialog) {
182             showMultiSimDialog(builder, listener);
183         } else {
184             showDisableDialog(builder, listener);
185         }
186     }
187 
showDisableDialog(Builder builder, DialogInterface.OnClickListener listener)188     private void showDisableDialog(Builder builder,
189             DialogInterface.OnClickListener listener) {
190         builder.setTitle(null)
191                 .setMessage(R.string.data_usage_disable_mobile)
192                 .setPositiveButton(android.R.string.ok, listener)
193                 .setNegativeButton(android.R.string.cancel, null);
194     }
195 
showMultiSimDialog(Builder builder, DialogInterface.OnClickListener listener)196     private void showMultiSimDialog(Builder builder,
197             DialogInterface.OnClickListener listener) {
198         final SubscriptionInfo currentSir = getActiveSubscriptionInfo(mSubId);
199         final SubscriptionInfo nextSir = getActiveSubscriptionInfo(
200                 SubscriptionManager.getDefaultDataSubscriptionId());
201 
202         final String previousName = (nextSir == null)
203             ? getContext().getResources().getString(R.string.sim_selection_required_pref)
204                 : SubscriptionUtil.getUniqueSubscriptionDisplayName(
205                         nextSir, getContext()).toString();
206 
207         builder.setTitle(R.string.sim_change_data_title);
208         builder.setMessage(getContext().getString(R.string.sim_change_data_message,
209                 String.valueOf(currentSir != null
210                     ? SubscriptionUtil.getUniqueSubscriptionDisplayName(currentSir, getContext())
211                     : null), previousName));
212 
213         builder.setPositiveButton(R.string.okay, listener);
214         builder.setNegativeButton(R.string.cancel, null);
215     }
216 
disableDataForOtherSubscriptions(int subId)217     private void disableDataForOtherSubscriptions(int subId) {
218         final SubscriptionInfo subInfo = getActiveSubscriptionInfo(subId);
219         if (subInfo != null) {
220             getContext().getSystemService(TelephonyManager.class).setDataEnabled(subId, false);
221         }
222     }
223 
224     @Override
onClick(DialogInterface dialog, int which)225     protected void onClick(DialogInterface dialog, int which) {
226         if (which != DialogInterface.BUTTON_POSITIVE) {
227             return;
228         }
229         if (mMultiSimDialog) {
230             getProxySubscriptionManager().get().setDefaultDataSubId(mSubId);
231             setMobileDataEnabled(true);
232             disableDataForOtherSubscriptions(mSubId);
233         } else {
234             // TODO: extend to modify policy enabled flag.
235             setMobileDataEnabled(false);
236         }
237     }
238 
239     @VisibleForTesting
240     final ProxySubscriptionManager.OnActiveSubscriptionChangedListener
241             mOnSubscriptionsChangeListener =
242             new ProxySubscriptionManager.OnActiveSubscriptionChangedListener() {
243                 public void onChanged() {
244                     if (DataUsageSummary.LOGD) {
245                         Log.d(TAG, "onSubscriptionsChanged");
246                     }
247                     updateEnabled();
248                 }
249             };
250 
251     /**
252      * Implementation of {@code MobileDataEnabledListener.Client}
253     */
254     @VisibleForTesting
onMobileDataEnabledChange()255     public void onMobileDataEnabledChange() {
256         updateChecked();
257     }
258 
259     public static class CellDataState extends BaseSavedState {
260         public int mSubId;
261         public boolean mChecked;
262         public boolean mMultiSimDialog;
263 
CellDataState(Parcelable base)264         public CellDataState(Parcelable base) {
265             super(base);
266         }
267 
CellDataState(Parcel source)268         public CellDataState(Parcel source) {
269             super(source);
270             mChecked = source.readByte() != 0;
271             mMultiSimDialog = source.readByte() != 0;
272             mSubId = source.readInt();
273         }
274 
275         @Override
writeToParcel(Parcel dest, int flags)276         public void writeToParcel(Parcel dest, int flags) {
277             super.writeToParcel(dest, flags);
278             dest.writeByte((byte) (mChecked ? 1 : 0));
279             dest.writeByte((byte) (mMultiSimDialog ? 1 : 0));
280             dest.writeInt(mSubId);
281         }
282 
283         public static final Creator<CellDataState> CREATOR = new Creator<CellDataState>() {
284             @Override
285             public CellDataState createFromParcel(Parcel source) {
286                 return new CellDataState(source);
287             }
288 
289             @Override
290             public CellDataState[] newArray(int size) {
291                 return new CellDataState[size];
292             }
293         };
294     }
295 }
296